- 회사 프로젝트에서 DB 스펙으로 Realm을 사용하고 있다.
RealmOptional
- Realm에서 옵셔널 값을 저장하려면 RealmOptional과 같이 타입을 지정해야 한다.
Custom Types
- Primitive가 아닌 커스텀 타입을 지정하려면 연산 프로퍼티를 사용할 수 있다.
private let lat = RealmOptional<Double>()
private let lng = RealmOptional<Double>()
var lastLocation: CLLocation? {
get {
guard let lat = lat.value, let lng = lng.value else {
return nil
}
return CLLocation(latitude: lat, longitude: lng)
}
set {
guard let location = newValue?.coordinate else {
lat.value = nil
lng.value = nil
return
}
lat.value = location.latitude
lng.value = location.longitude
}
}
Schema Relationships
To-one relationships
- 일대일 관계의 지정(Link)에서 항상 옵셔널이 되어야 한다.
- 원래 객체가 지워진다고 해서 연결된 객체가 자동으로 해제되지 않는다.
- 연결된 객체가 다른 곳에서라도 지워지면 참조하고 있던 원래 객체에서도 지워진다(weak reference)
To-many relationships (for objects)
- 일대다 관계에서는 List 자료형을 이용한다.
- List 자료형에서 Predicate 문법을 사용하여 filter가 가능하다.
LinkingObjects
- 내가 작업할 클래스가 다른 클래스의 관계가 지정되어 있는지를 반환해 준다.
- 링크된 관계가 없으면 안전하게 객체를 삭제할 수 있게 된다.
Reading & Writing Objects
Realm Results
- Results 타입은 lazy하게 fetching된다.
- 따라서 Results 타입은 항상 최신 데이터이다.
Writing objects
- modification은 항상 write transaction 내에서 이루어져야 한다.
try! realm.write {
// 변경할 작업
}
realm.beginWrite()
// 변경할 작업
// 잘못되면, 작업 취소 가능
realm.cancelWrite()
// 데이터 저장(커밋)
try! realm.commitWrite()
Notification & Reactive Apps
Notification
- Realm의 notification은 구독한 스레드에서만 전파된다.
- Runloop를 이용하기 때문에 Runloop이 도는 스레드에서 구독해야 한다(메인 스레드).
- token은 invalidate되거나 메모리에서 해제될 수 있다.
- object, collection, Realm 전체에 대해 observing이 가능하다.
- commitWrite(withoutNotifying: token)을 이용해 선택적으로 전달이 가능하다.
// Observe object
let token = article.observe { change in
switch change {
case .change(let properties):
if properties.contains(where: { $0.name == "date"}) {
print("date has changed from \(property.oldValue ?? "nil") to \(property.newValue ?? "nil")")
}
case .error(let error):
print("Error occurred: \(error)")
case .deleted:
print("Article was deleted")
}
}
// Observe collection
let token = article.people.observe { changes in
switch changes {
case .initial(let people):
print("Initial count: \(people.count)")
case .update(let people, let deletions, let insertions, let updates):
print("Current count: \(people.count)")
print("Inserted \(insertions), Updated \(updates), Deleted \(deletions)")
case .error(let error):
print("Error: \(error)")
}
}
// Observe entire Realm
let token = realm.observe { notification, realm in
print(notification)
}
Realm Configurations
Realm configuration options
- fileURL: Realm 파일이 저장될 위치 지정
- inMemoryIdentifier: 휘발성 Realm 생성(디스크에 쓰여지지 않음)
- syncConfiguration: 리얼타입 서버를 위한 설정
- encryptionKey: 암호화 키 설정
- readOnly: 읽기 전용 모드
- objectTypes: 저장하고 싶은 객체 리스트 지정, 기본 all
- schemaVersion: 스키마의 버전을 지정
- migrationBlock: 마이그레이션이 필요할 때 수행할 코드 블럭
- deleteRealmIfMigrationNeeded: 마이그레이션 없이 이전 데이터를 지움
- shouldCompactOnLaunch: 함수에서 true 반환시 압축
let newConfig = Realm.Configuration()
let realm = try! Realm(configuration: newConfig)
Multiple Realms / Shared Realms
Realm Povider
private static let cardsConfig = Realm.Configuration(
fileURL: try! Path.inLibrary("cards.realm"),
schemaVersion: 1,
deleteRealmIfMigrationNeeded: true,
objectTypes: [FlashCardSet.self, FlashCard.self])
// cards RealmProvider
public static var cards: RealmProvider = {
return RealmProvider(config: cardsConfig)
}()
private static let bundledConfig = Realm.Configuration(
fileURL: try! Path.inBundle("bundledSets.realm"),
readOnly: true,
objectTypes: [FlashCardSet.self, FlashCard.self])
// bundled RealmProvider
public static var bundled: RealmProvider = {
return RealmProvider(config: bundledConfig)
}()
// 사용
let cardsRealm = RealmProvider.cards.realm
let bundledSetsRealm = RealmProvider.bundled.realm
- RealmProvider를 사용해 여러 개의 realm에 액세스할 수 있다.
- RealmProvider를 사용하면 한번에 Realm을 관리할 수 있고, RealmProvider는 스레드를 넘어갈 수 있다.
- Realm 용량이 부족하거나, 다른 프로세스에 의해 점유된 경우 try! Realm에서 오류가 발생할 수 있다.