Realm

  • 회사 프로젝트에서 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에서 오류가 발생할 수 있다.