티스토리 뷰

Swift/Realm

[swift] Realm 시작하기

진태우 2020. 3. 26. 20:11

Realm을 적용하면서 공부한 내용을 잊기 전에 정리해 봅니다...

 

- 정의

Realm은 모바일에 최적화된 크로스 플랫폼 모바일 데이터베이스로, SQLite 및 Core Data의 대안입니다.

zero-copy design으로 설계되어 있어 ORM보다 훨씬 빠르며, 종종 raw SQLite보다 빠릅니다.

 

- zero-copy design

접근자 메서드를 호출하면 데이터베이스에서 read/write 할 수 있는 프록시 객체(쿼리 결과)를 반환받게 됩니다.

각 프록시 객체(쿼리 결과)는 데이터를 바라보고 있기 때문에 데이터베이스의 모든 변경 내용은 동일한 데이터를 가리키는 모든 객체에 반영됩니다. 때문에 새롭게 프록시 객체(쿼리 결과)를 가져올 필요가 없습니다.

Realm은 일반적으로 이런 동작을 zero-copy design 이라고 합니다.

 

아래 코드를 보면 이해하기 좀 더 수월할 수 있습니다.

let realm1 = try! Realm()
let realm2 = try! Realm()

print(realm1.isEmpty)        // true
print(realm2.isEmpty)        // true

let user = User(id: 1, name: "user1")

try! realm1.write {
  realm1.add(user)
}

// 변견 내용은 모든 프록시 객체에 반영됨
print(realm1.isEmpty)        // false
print(realm2.isEmpty)        // false

 

간단히 Todo 리스트를 보여주는 화면을 구성해 보겠습니다.

 

1. 모델

첫 시작은 모델을 만들어 주는 작업입니다.

// User Model

import RealmSwift

class User: Object {
  @objc dynamic var id: Int = 0
  @objc dynamic var name: String = ""
  let todos = List<Todo>()
  
  var hasTodos: Bool {
    return todos.count > 0
  }
  
  convenience init(id: Int, name: String) {
    self.init()
    self.id = id
    self.name = name
  }
  
  // 기본키 설정
  override class func primaryKey() -> String? {
    return "id"
  }
  
  // 재정의하여 인덱스에 추가 속성을 설정.
  override class func indexedProperties() -> [String] {
    return ["id", "name"]
  }
}

1.  기본 키 역할의 id, 이름을 나타내는 name을 선업합니다.

    객체 속성 중 하나를 기본 키로 설정할 수 있습니다. 일반적으로 고유하게 식별하는 속성은 기본 키의 주요 후보입니다.

2. todo 리스트를 저장하는 todos를 선언합니다.

    User 객체와 Todo 객체의 콜렉션을 연결하는 To-Many 관계를 만들었습니다.

3. id, name을 인덱스 속성으로 설정합니다.

    indexedProperties 메소드를 재정의하여 인덱스 속성을 설정할 수 있습니다.

    이는 데이터베이스를 필터링하거나 쿼리를 할 때의 시간을 개선하기 위해 사용됩니다.

    쓰기 속도는 약간 느리게 하므로, 상황에 맞게 사용하면 됩니다.

 

 

// Todo Model

import RealmSwift

class Todo: Object {
  @objc dynamic var title: String = ""
  @objc dynamic var desc: String = ""
  let ofUser = LinkingObjects(fromType: User.self, property: "todos")
  
  convenience init(title: String, desc: String) {
    self.init()
    self.title = title
    self.desc = desc
  }
}

1. title은 제목, desc은 자세한 설명을 나타냅니다.

2. LinkingObjects를 이용하여 User 객체에 대한 백링크(backlinks)를 만들었습니다.

    현재 Todo 객체에 어떤 User 객체가 연결되어 있는지 알려주는 속성입니다.

 

 

class IDCard: Object {
  @objc dynamic var idNumber: String = ""
  @objc dynamic var expiryDate: Date = Date.distantFuture
  let ofUser = LinkingObjects(fromType: User.self, property: "idCard")
  
  convenience init(idNumber: String) {
    self.init()
    self.idNumber = idNumber
  }
}

IDCard도 Todo와 같습니다.

 

 

 

모델을 정의하면서 한 가지 처음 보는 부분이 있었습니다.

속성 앞에 @objc dynamic을 왜 붙여야 하지? 라는 궁금중이 생겼습니다.

더보기

- @objc dynamic 을 붙이는 이유?!

realm 모델의 속성에는 @objc dynamic을 필수로 붙여야 합니다.

왜? 기본 데이터베이스 데이터에 대한 속성 접근자를 만들기 위해서!!

그래야 데이터베이스 데이터에 대해 접근이 가능해서 변경사항을 업데이트할 수 있다.

하지만 여기 예외가 있는데, LinkingObjects, List, RealmOptional 이 세 가지에 대해서는 동적으로 선언할 수 없다.

제네릭 속성은 dynamic속성의 동적 디스패치에 사용되는 Objective‑C 런타임에서 표현할 수 없으므로 이러한 속성은 동적으로 선언할 수 없습니다. 이러한 속성은 항상 let으로 선언해야 합니다.

예외에 대한 내용이 정확히 이해가 되진 않지만, 제네릭 속성은 정해진 타입이 아니기 때문에 런타임에서 표현할 수 없다고 하는 것 같다...

자세히 보려면 → https://realm.io/docs/swift/latest/#property-attributes

 

2. 객체 추가 (Add Object)

객체를 추가하는 방법을 3가지로 구현했습니다.

let realm = try! Realm()
    
// Todos
let todo1 = Todo(title: "todo1", desc: "test todo1")
let todo2 = Todo(title: "todo2", desc: "test todo2")
let todo3 = Todo(title: "todo3", desc: "test todo3")

// IDCard
let idCard1 = IDCard(idNumber: "0001")
let idCard2 = IDCard(idNumber: "0002")
let idCard3 = IDCard(idNumber: "0003")

// 1
let user1 = User(value: ["id": 1, "name": "user1", "idCard": idCard1])
user1.todos.append(todo1)

// 2
let user2 = User(id: 2, name: "user2")
user2.todos.append(todo2)
user2.idCard = idCard2

// 3
let user3 = User()
user3.id = 3
user3.name = "user3"
user3.todos.append(objectsIn: [todo1, todo2, todo3])
user3.idCard = idCard3

try! realm.write {
  realm.add([user1, user2, user3])
}

 

아래는 추가된 User, Todo 객체를 보여주는 그림입니다.

 

 

더보기

 

근데 데이터베이스 파일을 어떻게 확인하느냐?!

저장된 파일 경로를 아래와 같이 확인할 수 있습니다.

let fileURL = Realm.Configuration.defaultConfiguration.fileURL 
print(fileUrl)

// 이것은 device 경로
file:///var/mobile/Containers/Data/Application/{DeviceID}/Documents/default.realm

하지만 주로 simulator보다는 device를 사용하다보니 위에서 나오는 경로는 device에 저장된 경로가 출력됩니다.

simulator를 사용하면 위에서 확인된 경로로 들어가 보면 파일을 찾을 수 있고, device를 사용하면 아래와 같이 확인하면 됩니다.

 

Xcode → Window → Devices and Simulators 선택! (단축키는 command + shift + 2)

그럼 Device 화면이 나오고 INSTALLED APPS 라고 설치된 앱들이 보일 것입니다.

확인할 앱 선택 → 하단에 톱니바퀴 선택 → Download Container를 선택하면 그 앱의 패키지가 다운로드 됩니다.

다운로드 받은 패키지 파일 우클릭 → 패키지 내용보기 선택을 하면 아래와 그림과 같이 default.realm 파일을 확인할 수 있습니다!!

 

 

3. 객체 가져오기 (Fetch Object)

아래는 사용자 정보를 가져오는 코드입니다.

let realm = try! Realm()
let users = realm.objects(User.self)

// 기본키 사용
let user = realm.object(ofType: User.self, forPrimaryKey: 1)

// filtering objects
realm.objects(User.self).filter("name == 'user1'")        // 1
realm.objects(User.self).filter("name == [c] 'USER1'")    // 2
realm.objects(User.self).filter("id IN {1,2}")           // 3
realm.objects(User.self).filter("name BEGINSWITH 'u'")   // 4
realm.objects(User.self).filter("name CONTAINS 'er'")    // 5
realm.objects(User.self).filter("ANY todos.desc == 'test todo1'")  // 6

// 7
realm.objects(User.self)    
  .filter("name CONTAINS 'user'")
  .sorted(byKeyPath: "id")
  1. name이 user1과 일치하는 정보를 가져옵니다. (대소문자 구분함)
  2. 1번과 같지만 대소문자를 구분하지 않습니다.
  3. id가 1 또는 2를 가진 정보를 가져옵니다.
  4. name이 u로 시작하는 정보를 가져옵니다.
  5. name에 'er'이 포함된 정보를 가져옵니다.
  6. todos 리스트 객체 중에 desc가 'test todo1'과 일치하는 정보를 가져옵니다.
  7. 필터링한 내용을 id 순으로 정렬합니다.

위와 같이 여러 가지 필터링 방법이 있습니다.

고급 쿼리를 사용하려면 공식문서에서 보고 원하는 쿼리를 만들면 되겠습니다.

 

4. 객체 업데이트

객체를 추가할 때와 마찬가지로 쓰기 블록에서 수정을 해야 합니다.

쓰기 블록 외부에서 할 경우는 transaction 에러가 발생합니다.

let realm = try! Realm()
let user = realm.object(ofType: User.self, forPrimaryKey: 1)

try! realm.write {
  user.name = "user4"
}

 

5. 객체 삭제

객체를 삭제할 때도 쓰기 블록에서 해야 합니다.

아래와 같이 객체를 가져와서 삭제할 수 있습니다.

let realm = try! Realm()
let user = realm.object(ofType: User.self, forPrimaryKey: 1)

try! realm.write {
  realm.delete(user)
}

 

데이터베이스의 내용을 모두 지우려면 deleteAll 메소드를 사용하면 됩니다.

let realm = try! Realm()

try! realm.write {
  realm.deleteAll()
}

 

 

 - Reference

 

Realm (database) - Wikipedia

Realm is an open source object database management system, initially for mobile (Android/iOS),[1] also available for platforms such as Xamarin[2] or React Native,[3] and others,[4][5] including desktop applications (Windows[6]), and is licensed under the A

en.wikipedia.org

 

CRUD Operation Using RealmSwift Part 1

In this part we will Cover

medium.com

 

'Swift > Realm' 카테고리의 다른 글

[swift] Realm Notification  (0) 2020.03.27
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함