최신 영문 문서를 보시려면 이 곳을 참고하세요.

Swift 에서만 Realm을 사용하는 것을 찾고 있다면, Realm Swift를 사용하는 것을 권장합니다. Realm Objective‑C와 Realm Swift API는 함께 이용할 수 없으며, Realm Objective‑C와 Realm Swift를 동시에 사용하는 것은 지원하고 있지 않습니다.

Realm Objective‑C를 이용하면 효율적으로 안전하고 빠르고 지속적인 방법으로 앱의 모델 레이어를 작성할 수 있습니다. 아래의 예제를 참고하세요.

// Define your models like regular Objective‑C classes
@interface Dog : RLMObject
@property NSString *name;
@property NSData   *picture;
@property NSInteger age;
@end
@implementation Dog
@end
RLM_ARRAY_TYPE(Dog)
@interface Person : RLMObject
@property NSString             *name;
@property RLMArray<Dog *><Dog> *dogs;
@end
@implementation Person
@end

// Use them like regular Objective‑C objects
Dog *mydog = [[Dog alloc] init];
mydog.name = @"Rex";
mydog.age = 1;
mydog.picture = nil; // properties are nullable
NSLog(@"Name of dog: %@", mydog.name);

// Query Realm for all dogs less than 2 years old
RLMResults<Dog *> *puppies = [Dog objectsWhere:@"age < 2"];
puppies.count; // => 0 because no dogs have been added to the Realm yet

// Persist your data easily
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
  [realm addObject:mydog];
}];

// Queries are updated in realtime
puppies.count; // => 1

// Query and update the result in another thread
dispatch_async(dispatch_queue_create("background", 0), ^{
  @autoreleasepool {
    Dog *theDog = [[Dog objectsWhere:@"age == 1"] firstObject];
    RLMRealm *realm = [RLMRealm defaultRealm];
    [realm beginWriteTransaction];
    theDog.age = 3;
    [realm commitWriteTransaction];
  }
});

지금 Core Data를 사용하는데 Realm으로 바꾸기를 고려하고 있다면, 이를 어떻게 해결하는 지에 대한 우리 기사를 참고하세요. 관련 기사

시작하기

Realm Objective‑C 다운로드 또는 소스코드를 GitHub에서 볼 수 있습니다.

요구사항

  • iOS 7 이상 또는 macOS 10.9 이상 및 모든 버전의 tvOS와 watchOS.

  • Xcode 8.0 이상 버전이 필요합니다. Realm Objective‑C 2.3.0까지만 Swift 2.x 버전과 Xcode 7.3 버전을 지원합니다.

설치

주의 : “Dynamic Framework”는 iOS 7과 호환되지 않습니다. iOS 7 지원을 지원하는 경우 “Static Framework” 을 참고하세요.

  1. Realm의 최신버전을 다운로드하고, zip 압축을 풉니다.
  2. Xcode 프로젝트에서 “General” 탭으로 이동합니다. ios/dynamic/, osx/, tvos/ 또는 watchos/의 디렉터리에서 “Embedded Binaries” 섹션으로 Realm.framework를 넣습니다. Copy items if needed 를 선택했는지 확인하고(프로젝트에서 Realm을 여러 플랫폼에서 활용하는 경우는 제외) Finish 버튼을 누릅니다.
  3. 유닛 테스트 타깃의 “Build Settings” 탭에서 “Framework Search Paths” 부분에 Realm.framework의 parent 경로를 추가하세요. 4.Swift와 함께 Realm을 사용한다면, Xcode 프로젝트에서 Swift/RLMSupport.swift 파일을 File Navigator에 넣으신 후 Copy items if needed 를 선택합니다.
  4. iOS, watchOS 또는 tvOS 프로젝트에서 Realm을 사용한다면, 앱 타깃의 “Build Phases” 탭에서 “Run Script Phase”를 새로 생성하고 Script 텍스트 필드에 다음 코드를 붙여넣습니다.
bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh"

이 단계를 통해 universal binaries를 아카이빙할 때 발생하는 App Store submission bug를 해결할 수 있습니다.

  1. CocoaPods 0.39.9 버전 또는 그 상위버전을 설치하세요.
  2. CocoaPods가 최신 Realm 버전을 인식할 수 있도록 pod repo update를 실행합니다.
  3. Podfile을 연 다음 앱 타깃에 pod ‘Realm’을 추가하고 테스트 타깃에 pod ‘Realm/Headers’를 추가하세요.
  4. 커맨드라인을 통해 pod install을 실행하세요.
  5. 이후에는 .xcodeproj 파일 대신 CocoaPods 에 의해 생성된 .xcworkspace 파일을 이용해 작업하세요.
  6. Swift와 함께 Realm을 사용한다면 Swift/RLMSupport.swift 파일을 Xcode 프로젝트의 파일 내비게이션에 넣은 후 Copy items if needed 체크박스를 체크합니다.
  1. Carthage 0.17.0 버전 또는 그 상위버전을 설치하세요.
  2. github "realm/realm-cocoa"를 Cartfile에 추가합니다.
  3. carthage update를 실행합니다.
  4. Carthage/Build/ 내의 타깃 플랫폼 디렉터리의 Realm.framework를 Xcode 프로젝트의 “General” 세팅의 “Linked Frameworks and Libraries” 영역으로 옮깁니다.
  5. iOS/watchOS/tvOS: application targets의 “Build Phases” 세팅 탭에서 “+” 아이콘을 누르고 “New Run Script Phase”를 선택한 후 아래처럼 Run Script를 생성합니다.

    /usr/local/bin/carthage copy-frameworks

    그리고 “Input Files”에 사용할 프레임워크의 경로를 추가합니다. 예를 들면:

    $(SRCROOT)/Carthage/Build/iOS/Realm.framework

    이 단계를 통해 universal binaries를 아카이빙할 때 발생하는 App Store submission bug를 해결할 수 있습니다.

  6. Swift에서 Realm을 사용 중이라면, Swift/RLMSupport.swift 파일을 Xcode 프로젝트의 파일 네비게이션에 넣은 후 Copy items if needed 체크박스를 체크합니다. checkbox.
  1. Realm의 최신 버전을 다운로드하고 압축을 풉니다.
  2. ios/static/ 디렉터리에서 Realm.framework 을 선택하여 Xcode 프로젝트의 File Navigation에 넣습니다. 이때, Copy items if needed 이 선택된지 확인하고, Finish 버튼을 누릅니다.
  3. Xcode의 File Navigator에서 프로젝트를 클릭합니다. 어플리케이션 대상을 선택하고 Build Phases 탭으로 이동합니다. Link Binary with Libraries 의 +를 클릭하여 libc++.tbdlibz.tbd를 추가합니다.
  4. Swift에서 Realm을 사용 중이라면, Swift/RLMSupport.swift 파일을 Xcode의 File Navigator에 넣은 후 Copy items if needed 를 선택합니다.

Realm 프레임워크 가져오기

Objective‑C 소스 파일 시작 부분에 #import <Realm/Realm.h>를 선언해서 Realm Objective‑C를 가져오고 코드에서 사용할 수 있도록 합니다. 혹시 Swift 파일이 있다면 Swift 소스 파일 맨 위에서 import Realm를 사용합니다. 이제 시작 준비를 모두 마쳤습니다.

Swift에서 Realm Objective‑C 사용하기

Swift에서만 Realm을 사용한다면 이 챕터 대신 Realm Swift를 참고하세요.

Realm Objective‑C는 Objective‑C와 Swift가 혼용되는 프로젝트에서 잘 작동합니다. Swift에서도 모델을 정의하거나 Realm의 Objective‑C API를 사용하는 등, Objective‑C로 만든 Realm의 기능을 모두 활용할 수 있습니다. 다만 Objective‑C만 사용하는 프로젝트와의 차이점이 일부 존재합니다.

RLMSupport.swift

Swift/RLMSupport.swift 파일을 컴파일 합니다. (release zip에서도 다운로드 할 수 있습니다.)

이 파일은 Sequence 적합성을 Realm Objective‑C collection 타입에 추가하고 variadic arguments를 포함하는 메서드처럼 원래 Swift에서 접근할 수 없는 Objective‑C 메서드에 접근할 수 있도록 합니다.

Swift를 사용하지 않더라도 Realm Objective‑C의 모든 사용자가 무거운 Swift 다이내믹 라이브러리를 포함하게 될 수 있으므로, Realm Objective‑C 기본 설정에서는 이 파일을 포함하지 않습니다.

RLMArray 속성

Objective‑C에서는 RLMArray1 대 다의 관계내에 포함된 객체 타입을 파악하기 위해 프로토콜 적합성을 사용합니다. Swift에서는 이런 문법이 불가능하므로 대신 RLMArray 속성을 아래와 같이 선언해야 합니다.

class Person: RLMObject {
  dynamic var dogs = RLMArray(objectClassName: Dog.className())
}

Objective‑C에서는 아래 예제와 같이 사용합니다.

@interface Person : RLMObject
@property RLMArray<Dog *><Dog> *dogs;
@end

tvOS

tvOS에서 Documents 디렉터리에 저장하는 것이 금지돼 있으므로, Realm의 기본 저장소는 NSCachesDirectory으로 설정됩니다. 그러나 어느 때고 tvOS가 캐시 디렉터리의 파일을 제거할 수 있으므로 중요한 사용자 데이터를 저장하는 용도보다는 재건 가능한 캐시로 Realm을 사용하는 것을 권장합니다.

tvOS 앱과 Top Shelf extension 등의 TV 시리즈 익스텐션 간의 Realm 파일을 공유하고 싶다면 애플리케이션 그룹을 위한 shared container 내의 Library/Caches/ 디렉터리를 사용해야 합니다.

// end declarations
RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
configuration.fileURL = [[[NSFileManager defaultManager]
    containerURLForSecurityApplicationGroupIdentifier:@"group.io.realm.examples.extension"]
    URLByAppendingPathComponent:@"Library/Caches/default.realm"];

또한 미리 만들어진 Realm 파일을 앱에 번들로 넣을 수도 있지만, 앱 스토어 가이드라인을 지켜서 앱의 크기를 200MB 이하로 유지해야 합니다.

Realm을 오프라인 캐시나 미리 로드된 데이터와 함께 사용하는 방법을 보여주는 tvOS 샘플 앱은 tvOS examples에서 확인할 수 있습니다.

Realm 브라우저

Realm Swift 0.96이나 그 이상 버전에서 사용하는 경우, 현재 Mac App Store 버전에서는 Realm 파일을 열 수 없습니다. GitHub에서 최신 Realm Browser 를 다운로드 하여 이용하시기 바랍니다.

RealmBrowser는 .realm 데이터베이스를 읽고, 편집을 할 수 있는 Mac 응용 프로그램입니다.

Realm Browser

Tools > Generate demo database 메뉴를 통해서 테스트용 데이터베이스와 샘플 데이터가 포함된 Realm 데이터베이스를 생성할 수 있습니다.

Realm 파일을 찾는데 도움이 필요하다면 StackOverflow 답변을 참고하세요.

Realm 브라우저는 Mac App Store에서 이용가능합니다. Realm Swift 0.96이나 그 이상 버전에서 사용하는 경우, 현재의 Mac App Store 버전에서는 Realm 파일을 열 수 없습니다. GitHub에서 최신 Realm Browser 를 다운로드 하여 이용하시기 바랍니다.

Xcode 플러그인

제공되는 Xcode 플러그인은 새로운 Realm 모델을 쉽게 만들 수 있도록 도와줍니다.

Realm Plugin

Realm의 Xcode 플러그인을 설치하는 가장 쉬운 방법은 Alcatraz를 이용하여 “RealmPlugin” 을 설치하는 방법입니다. 물론 릴리즈를 통해 제공하는 plugin/RealmPlugin.xcodeproj 을 열어서 직접 설치하여 빌드도 가능합니다. 설치된 플러그인을 확인하기 위해서는 Xcode 재실행이 필요합니다. Xcode 메뉴에서 새 파일을 생성 (File > New > File… — or ⌘N) 할 때, 새 Realm 모델을 생성하는 옵션을 볼 수 있습니다.

API 레퍼런스

Realm에서 사용할 수 있는 모든 클래스와 메소드에 대해서는 API Reference 를 참조하십시오.

예제

최신 Realmexamples/에서 Objective‑C, Swift 그리고 RubyMotion 어플리케이션의 예제를 확인 할수 있습니다. 예제에는 마이그레이션, UITableViewController의 사용법, 암호화, 명렁어 툴 및 그 이외의 Realm의 여러 기능 사용법을 포함하고 있습니다.

도움을 얻으려면

  • 코드와 관련하여 도움이 필요한가요? StackOverflow에 물어보세요. 우리는 적극적으로 모니터링과 질문에 대한 답을 해드립니다.
  • 버그를 알리고 싶나요? 우리 저장소에 이슈를 등록하세요. 가능하면 Realm의 버전과 전체 로그, Realm 파일, 프로젝트를 포함하여 이슈에 보여주세요.
  • 기능 요청이 있나요? 우리 저장소에 이슈를 등록하세요. 우리에게 이 기능이 무엇을 하는지, 왜 필요한지를 말씀해주세요. 만약 Crashlytics나 HockeyApp와 같은 크래시 리포터를 사용한다면 로그 컬렉션을 활성화하세요. Realm은 예외가 발생해서 회복 불가능한 상황일 때 (사용자 데이터가 아닌) 메타데이터 정보를 기록하며, 이런 기록은 에러 상황을 디버깅하는데 도움이 됩니다.

모델

Realm 데이터 모델은 property로 일반적인 Objective‑C 클래스들을 사용하여 정의할 수 있습니다. 간단하게 서브 클래스 RLMObject나 존재하는 모델 클래스를 Realm 데이터 모델 객체로 만들 수 있습니다. Realm 모델 객체는 다른 Objective‑C의 객체와 기능이 대체로 같습니다. 자신만의 메소드와 프로토콜을 추가할 수 있고 다른 객체와 동일하게 사용할 수 있습니다. 주요 제한은 생성한 스레드 안에서만 사용할수 있다는 점과 인스턴스 변수에 직접적으로 다른 관련된 속성에 접근할 수 없다는 점입니다.

만약 이미 제공 중인 Xcode 플러그인을 설치하였다면 “New File…” 다이얼로그를 통해서 인터페이스와 동작하는 파일의 템플릿을 확인 할 수 있습니다.

관계와 자료구조는 타깃 타입의 속성이나 RLMArray의 객체 리스트를 포함하여 간단하게 정의 할 수 있습니다.

#import <Realm/Realm.h>

@class Person;

// Dog model
@interface Dog : RLMObject
@property NSString *name;
@property Person   *owner;
@end
RLM_ARRAY_TYPE(Dog) // define RLMArray<Dog>

// Person model
@interface Person : RLMObject
@property NSString             *name;
@property NSDate               *birthdate;
@property RLMArray<Dog *><Dog> *dogs;
@end
RLM_ARRAY_TYPE(Person) // define RLMArray<Person>

// Implementations
@implementation Dog
@end // none needed

@implementation Person
@end // none needed

실행할 때 Realm이 코드에 명시된 모든 모델을 분석하므로 사용하지 않는 모델이더라도 문법적으로 유효해야 합니다.

Swift에서 Realm을 사용할 경우, Swift.reflect(_:) 펑션은 모델의 정보를 결정하는데 사용되며, init()에 성공해야 합니다. 즉, 모든 non-optional 속성이 기본값을 가져야 합니다.

더 자세한 내용은 RLMObject 에서 확인하세요.

지원하는 데이터 유형

Realm이 지원하는 속성 타입은 다음과 같습니다: BOOL, bool, int, NSInteger, long, long long, float, double, CGFloat, NSString, NSDate, NSData, 특정 타입으로 태그된 NSNumber.

CGFloat은 플랫폼이 독립적이지 않기 때문에 사용하지 않도록 하세요.

1 대 1 또는 1 대 다 같은 관계를 모델링할 수 있는 RLMArray<Object *><Object>RLMObject 서브 클래스를 사용할 수 있습니다.

RLMArrays은 Xcode 7 이상에서 compile-time Objective‑C generics를 지원합니다. 다음은 속성 정의의 다른 구성요소가 무엇을 의미하는지와 왜 그것이 도움이 되는지에 대한 설명입니다:

  • RLMArray: The property type.
  • <Object *>: Generic specialization. 이것은 컴파일 시간에 잘못된 객체을 가진 배열을 사용하는 것을 막는 걸 도와줍니다.
  • <Object>: 프로토콜을 따르는 RLMArray. 이것은 Realm 이 어떻게 런타임시 이 모델의 스키마를 specialize 하는지 알려주게 합니다.

관계

RLMObjectRLMObjectRLMArray 속성 사용으로 서로 연결할 수 있습니다. RLMObjectNSArray와 매우 유사한 인터페이스를 제공하고 RLMArray에 포함된 객체는 인덱스 첨자로 접근할 수 있습니다. NSArray와 달리, RLMArray는 타입을 강제하고 하나의 RLMObject 서브 클래스만 갖도록 설계되었습니다. 자세한 내용은 RLMArray를 참조하세요.

Person 모델(앞 부분을 참고하세요)을 정의했으니 Dog 모델을 만들어 보겠습니다.

// Dog.h
@interface Dog : RLMObject
@property NSString *name;
@end

1 대 1의 관계

다 대 일 혹은 일 대 일 관계를 대상으로 간단하게 RLMObject 서브 클래스 타입으로 속성을 정의합니다:

// Dog.h
@interface Dog : RLMObject
// ... other property declarations
@property Person *owner;
@end

다른 속성과 마찬가지로 이 속성도 사용할 수 있습니다.

Person *jim = [[Person alloc] init];
Dog    *rex = [[Dog alloc] init];
rex.owner = jim;

RLMObject 속성을 사용할 때, 일반 속성 문법으로 중첩 속성을 접근할 수 있습니다. 예를 들면, rex.owner.address.country 예제는 객체그래프를 탐색하고 자동적으로 Realm으로부터 필요한 객체를 반환합니다.

1 대 다의 관계

RLMArray속성으로 1 대 다의 관계를 정의할 수 있습니다. RLMArray는 하나의 RLMObject 타입을 갖고 NSMutableArray와 유사한 인터페이스를 제공합니다.

A RLMArray는 기본 키가 있는 객체를 포함, 같은 Realm 객체에 대한 여러 개의 참조를 포함할 수 있습니다. 예를 들어 빈 RLMArray를 만들고 같은 객체를 세 번 중복해서 넣을 수 있습니다. RLMArray에 인덱스 0, 1, 2로 접근하면 해당 객체를 반환합니다.

“dogs” 속성을 Person 모델에 추가하여 복수의 dogs와 연결하려면 RLMArray<Dog>타입을 먼저 정의해야 합니다. 이것은 모델 인터페이스에 해당하는 아래의 매크로를 통해 완료됩니다:

// Dog.h
@interface Dog : RLMObject
// ... property declarations
@end

RLM_ARRAY_TYPE(Dog) // Defines an RLMArray<Dog> type

RLM_ARRAY_TYPE 매크로는 RLMArray<Dog> 구문의 사용을 가능하게 하는 프로토콜을 만듭니다. 사용 가능하도록 하기 위해 타입을 앞에 선언해야만 합니다.

RLMArray<Dog>의 속성을 정의할 수 있습니다:

// Person.h
@interface Person : RLMObject
// ... other property declarations
@property RLMArray<Dog *><Dog> *dogs;
@end

RLMArray 속성에 이와 같이 접근하고 할당 할 수 있습니다:

// Jim is owner of Rex and all dogs named "Fido"
RLMResults<Dog *> *someDogs = [Dog objectsWhere:@"name contains 'Fido'"];
[jim.dogs addObjects:someDogs];
[jim.dogs addObject:rex];

주의해야하는 것은 RLMArray에는 nil을 대입하는 것은 가능하지만, 그것은 비우는 것을 의미하고, 삭제를 하면 안됩니다. 즉, nil을 대입해도 비어있을 뿐, RLMArray에 언제든지 객체를 추가 할 수 있습니다.

RLMArray 속성은 삽입 순서를 보장합니다.

역관계

객체 간의 연결은 방향성을 가집니다. 즉 Person.dogsDog 객체에 1 대 다의 관계로 연결되고, Dog.ownerPerson에 1 대 1의 관계로 연결된다면, 이들 연결 관계는 각각 독립적입니다. Person 객체의 dogs 속성에 Dog를 삽입해도 dog의 owner 속성에 해당 Person을 자동적으로 연결해주지 않습니다. 수동으로 관계의 쌍을 동기화한다면 에러가 발생하기 쉽고, 복잡한데다 정보를 중복으로 생성하기 때문에, Realm은 객체 속성의 연결을 통해 역관계를 표현하도록 합니다.

객체 속성의 연결 관계를 통해, 특정 속성으로 주어진 객체와 연결된 모든 객체를 조회할 수 있습니다. 예를 들어 Dog 객체는 owners라는 이름의 속성을 가질 수 있으며 이는 dogs 속성에 동일한 Dog 객체를 지닌 Person 객체를 가질 수 있습니다. RLMLinkingObjects 타입인 owners 속성을 만들고 +[RLMObject linkingObjectsProperties]를 오버라이딩해서 ownersPerson 모델 객체를 지시하도록 하면 됩니다.

@interface Dog : RLMObject
@property NSString *name;
@property NSInteger age;
@property (readonly) RLMLinkingObjects *owners;
@end

@implementation Dog
+ (NSDictionary *)linkingObjectsProperties {
    return @{
        @"owners": [RLMPropertyDescriptor descriptorWithClass:Person.class propertyName:@"dogs"],
    };
}
@end

Optional 속성

NSString *, NSData *, 과 NSDate * 형의 속성은 기본적으로 nil을 설정하는 것이 가능합니다. 만약에 nil을 설정하는 것을 막고, 항상 값이 존재하는 것으로 보장하려면, +requiredProperties 메서드를 재정의 하면 됩니다.

예를 들면, 아래의 모델 정의에서 Person 객체의 name 속성에 nil을 저장하려고 하면 예외가 발생합니다. 그러나 birthday 속성에 nil을 설정하고 저장하는 것이 가능합니다:

@interface Person : RLMObject
@property NSString *name;
@property NSDate *birthday;
@end

@implementation Person
+ (NSArray *)requiredProperties {
    return @[@"name"];
}
@end

optional 수를 저장하는 것은 NSNumber *속성을 사용하면 됩니다. Realm은 각 숫자의 크기에 따라 다른 내부 표현을 사용하고 있기 때문에, RLMInt, RLMFloat, RLMDouble, 또는 RLMBool 을 사용하여 지정해야 합니다. 할당 된 값은 지정된 형식으로 변환됩니다.

NSDecimalNumber 값은 RLMDouble Realm 속성에만 할당될 수 있으며 Realm은 기본 십진수 값이 아닌 값의 배정 밀도 부동 소수점 근사값을 저장합니다.

예를 들면, Person 객체에 대해서 birthday 대신에 age를 저장합니다. 나이를 알 수 없는 경우 nil을 사용할 수 있습니다:

@interface Person : RLMObject
@property NSString *name;
@property NSNumber<RLMInt> *age;
@end

@implementation Person
+ (NSArray *)requiredProperties {
    return @[@"name"];
}
@end

RLMObject의 서브 클래스의 속성은 항상 nil일 수 있습니다. 따라서 requiredProperties 에 지정할 수 없습니다. 그리고 RLMArray형의 속성은 nil을 지원하고 있지 않습니다.

속성표

모델 속성 선언에 유용한 레퍼런스를 아래 테이블에서 확인하세요.

Type Non-optional Optional
Bool @property BOOL value; @property NSNumber<RLMBool> *value;
Int @property int value; @property NSNumber<RLMInt> *value;
Float @property float value; @property NSNumber<RLMFloat> *value;
Double @property double value; @property NSNumber<RLMDouble> *value;
String @property NSString *value; 1 @property NSString *value;
Data @property NSData *value; 1 @property NSData *value;
Date @property NSDate *value; 1 @property NSDate *value;
Object n/a: must be optional @property Object *value;
List @property RLMArray<Object *><Object> *value; n/a: must be non-optional
LinkingObjects @property (readonly) RLMLinkingObjects<Object *> *value; 2 n/a: must be non-optional

1) Objective‑C 레퍼런스 타입의 Required 속성은 다음처럼 선언합니다.

@implementation MyModel
+ (NSArray *)requiredProperties {
    return @[@"value"];
}
@end

2) 연결된 객체 속성은 +linkingObjectsProperties 메서드를 사용해서 선언합니다.

@implementation MyModel
+ (NSDictionary *)linkingObjectsProperties {
    return @{ @"property": [RLMPropertyDescriptor descriptorWithClass:Class.class propertyName:@"link"] };
}
@end

속성 특성

주의, Realm은 Objective‑C의 속성 특성 중에 nonatomic, atomic, strong, copy, weak, 등을 무시합니다. Realm은 내부적으로 이미 최적화된 스토리지 방법론을 가지고 있기 때문입니다. 그래서 오해의 소지를 피하기 위해서 이러한 속성의 특성을 모델에 사용하지 않을 것을 권고합니다. 그러나 만약 속성 특성을 설정하였다면 Realm에 RLMObject가 추가되기 전까지는 사용할 수 있습니다. 사용자 지정의 getter와 setter는 RLMObject가 Realm에 의해 관리되는지 여부와 관계 없이 사용할 수 있습니다.

인덱스 속성

클래스 메소드의 +indexedProperties 를 재정의하면 인덱스에 추가 속성을 지정할 수 있습니다:

@interface Book : RLMObject
@property float price;
@property NSString *title;
@end

@implementation Book
+ (NSArray *)indexedProperties {
  return @[@"title"];
}
@end

문자열, 정수형, boolean,NSDate 속성인 경우 인덱스로 지정할 수 있습니다. 속성에 인덱싱을 하면, = 또는 IN 을 사용한 쿼리의 속도는 크게 향상됩니다. 그 대신에 객체 생성은 조금 느립니다.

기본 속성 값

클래스 메소드의 +defaultPropertyValues를 재정의하여 속성에 초기 값을 지정할 수 있습니다. Swift는 표준 속성의 초기값을 설정하는 방법이 제공되고 있기 때문에 defaultPropertyValues()가 아닌 표준 방법을 제공합니다.

@interface Book : RLMObject
@property float price;
@property NSString *title;
@end

@implementation Book
+ (NSDictionary *)defaultPropertyValues {
    return @{@"price" : @0, @"title": @""};
}
@end

객체 자동 업데이트

RLMObject 객체는 기조 데이터를 바로 반영하고 자동 업데이트하므로 객체를 새로고침할 필요가 없습니다. 한 객체의 속성을 고치면 동일 객체를 참조하는 다른 객체에도 즉각적으로 반영됩니다.

Dog *myDog = [[Dog alloc] init];
myDog.name = @"Fido";
myDog.age = 1;

[realm transactionWithBlock:^{
  [realm addObject:myDog];
}];

Dog *myPuppy = [[Dog objectsWhere:@"age == 1"] firstObject];
[realm transactionWithBlock:^{
  myPuppy.age = 2;
}];

myDog.age; // => 2

RLMObject의 이런 측면은 Realm의 빠르고 효율적인 성능뿐만 아니라, 보다 단순하고 반응이 빠른 코드를 작성하는데도 도움이 됩니다. 예를 들어 특정 Realm 객체에 종속적인 UI 코드가 있다면 UI redraw가 작동하기 전에 데이터를 새로 고침하고 다시 가져오는 작업을 직접 하지 않아도 됩니다.

Realm 알림을 구독하면 객체 내의 Realm 데이터가 업데이트되는 시점을 파악하고 UI가 갱신될 시점을 지정할 수 있습니다. 다른 방법으로는 key-value observation을 사용해서 RLMObject의 특정 속성이 업데이트되는 시점을 통지받을 수도 있습니다.

기본키

클래스 메소드의 +primaryKey 를 재정의하면 그 모델의 기본키를 지정할 수 있습니다. 기본키를 사용하여 객체를 효율적으로 검색하고 업데이트 할 수 있고 고유성을 유지할 수 있습니다. 한 객체에 하나의 기본키가 Realm에 추가되면, 기본키는 변경될 수 없습니다.

@interface Person : RLMObject
@property NSInteger id;
@property NSString *name;
@end

@implementation Person
+ (NSString *)primaryKey {
    return @"id";
}
@end

저장하지 않는 속성

클래스 메소드의 ignoredProperties을 재정의함으로써 Realm에 저장되지 않는 속성을 지정할 수 있습니다. 저장하지 않는 속성은 Realm은 속성의 조작에 개입하지 않습니다. 즉 보통의 속성으로 인스턴스 변수에 값을 저장하고 getter / setter 메소드를 자유롭게 재정의 할 수 있습니다.

@interface Person : RLMObject
@property NSInteger tmpID;
@property (readonly) NSString *name; // read-only properties are automatically ignored
@property NSString *firstName;
@property NSString *lastName;
@end

@implementation Person
+ (NSArray *)ignoredProperties {
    return @[@"tmpID"];
}
- (NSString *)name {
    return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}
@end

저장하지 않는 속성은 일반적인 Objective-C나 Swift 클래스의 일반 속성과 정확히 같은 방식으로 동작합니다. 또한 Realm에서 제공하는 기능은 지원하지 않습니다. 예를 들어 쿼리에서 사용할 수 없고 동일한 Realm 객체를 가리키는 다른 인스턴스에서 해당 속성을 변경해도 변경 알림을 실행하지 않습니다. 하지만 KVO를 사용해서 관찰할 수는 있습니다.

모델 상속

Realm은 모델 상속을 허용해서 모델 간의 코드 재사용을 가능하게 하지만, 런타임 시의 클래스 다형성과 관련된 일부 Cocoa 기능은 불가능할 수 있습니다. 모델 상속이 가능한 항목은 아래와 같습니다.

  • 부모 클래스가 자식 클래스에 상속해 준 클래스 메서드, 객체 메서드와 속성
  • 부모 클래스를 인자로 가진 메서드와 함수의 서브 클래스내 작동

모델 상속이 불가능한 항목은 다음과 같습니다.

  • 다형 클래스 간의 캐스팅 (예: 서브 클래스에서 서브 클래스, 서브 클래스에서 부모 클래스, 부모 클래스에서 서브 클래스 등)
  • 동시에 다수 클래스에게 쿼리
  • 멀티 클래스를 담는 컨테이너 (RLMArray, RLMResults)

이런 기능은 추후 개발 로드맵에 따라 Realm에 포함될 예정으로, 현재로서는 자주 발생하는 문제의 회피를 위해 샘플 코드를 참고하시기 바랍니다.

대안으로는 아래와 같은 클래스 조합 패턴을 사용하여 다른 클래스를 포함하는 서브 클래스 로직을 구성할 수도 있습니다.

// Base Model
@interface Animal : RLMObject
@property NSInteger age;
@end
@implementation Animal
@end

// Models composed with Animal
@interface Duck : RLMObject
@property Animal *animal;
@property NSString *name;
@end
@implementation Duck
@end

@interface Frog : RLMObject
@property Animal *animal;
@property NSDate *dateProp;
@end
@implementation Frog
@end

// Usage
Duck *duck =  [[Duck alloc] initWithValue:@{@"animal" : @{@"age" : @(3)}, @"name" : @"Gustav" }];

컬렉션

Realm은 객체의 그룹을 나태내는 여러 타입을 제공하며, 이를 “Realm 컬렉션”이라 합니다.

  1. RLMResults, 쿼리를 통해 반환된 객체를 나타내는 클래스.
  2. RLMArray, 모델의 1 대 다 관계를 나타내는 클래스.
  3. RLMLinkingObjects, 모델의 역관계를 나타내는 클래스.
  4. RLMCollection, 모든 Realm 컬렉션이 따르는 공통 인터페이스를 정의하는 프로토콜.

Realm 컬렉션은 일관성 유지를 보장하도록 RLMCollection 프로토콜을 따릅니다. 이 프로토콜은 NSFastEnumeration을 상속하므로 다른 Foundation의 컬렉션 사용법과 동일하게 사용할 수 있습니다. 쿼리나 소팅, 병합 등 부차적인 공통 Realm 컬렉션 API는 이 프로토콜에 정의됩니다. RLMArray는 프로토콜 인터페이스 이상의 객체 추가나 삭제와 같은 부가적인 뮤테이션 기능을 가집니다.

RLMCollection 프로토콜을 사용해서 Realm 컬렉션을 활용하는 일반적인 코드를 작성할 수 있습니다.

@implementation MyObject
- (void)operateOnCollection:(id<RLMCollection>)collection {
  // Collection could be either RLMResults or RLMArray
  NSLog(@"operating on collection of [email protected]", collection.objectClassName);
}
@end

쓰기

모든 객체의 변경(추가, 수정, 삭제)는 쓰기 트랜잭션 내에서 처리됩니다.

Realm 객체는 보통의 Objective‑C 객체와 같이 Realm의 관리를 받지 않는 상태(예를 들어 Realm에 아직 저장되지 않는 상태)로 객체화 및 사용이 가능합니다. 하지만 스레드 사이에 객체를 공유하거나 실행 중인 앱 간에 재사용을 하기 위해서는 쓰기 트랜잭션 내에서 수행되는 작업으로 Realm에 객체를 저장해야만 합니다. 트랜잭션은 무시할 수 없는 오버헤드가 발생하기 때문에 가능한 트랜잭션의 수는 최소화 하는 것이 바람직합니다.

트랜잭션은 disk I/O를 동시에 수행하는 경우엔 실패할 수 있기 때문에, -[RLMRealm transactionWithBlock:]-[RLMRealm commitWriteTransaction]메소드는 필요에 따라 NSError 매개 변수를 전달할 수 있으며, 디스크 용량 부족 등으로 실패했을 경우에 오류에서 복구할 수 있습니다. 간단하게 하기 위해서 이 문서와 샘플 코드에서는 오류 처리를 하지 않지만, 실제 앱에서는 오류를 처리하고 필요에 따라 복구를 해야합니다.

객체 생성

RLMObject의 서브 클래스로 정의한 모델을 인스턴스화하여 새로운 객체로 Realm에 저장합니다. 이 간단한 모델을 생각해 볼 수 있습니다:

// Dog model
@interface Dog : RLMObject

@property NSString *name;
@property NSInteger age;

@end

// Implementation
@implementation Dog
@end

우리는 여러 가지 방법으로 새로운 객체를 만들 수 있습니다:

// (1) Create a Dog object and then set its properties
Dog *myDog = [[Dog alloc] init];
myDog.name = @"Rex";
myDog.age = 10;

// (2) Create a Dog object from a dictionary
Dog *myOtherDog = [[Dog alloc] initWithValue:@{@"name" : @"Pluto", @"age" : @3}];

// (3) Create a Dog object from an array
Dog *myThirdDog = [[Dog alloc] initWithValue:@[@"Pluto", @3]];
  1. 가장 명백한 건 (Objective‑C에서) alloc-init을 사용하거나 (Swift에서) 지정된 초기화를 사용하는 것입니다. 모든 속성들은 객체가 Realm에 추가되기 전에 설정해야 함을 유의하세요.
  2. 객체는 고유한 키와 값을 사용하여 dictionary에 만들 수 있습니다.
  3. 마지막으로 RLMObject 서브 클래스는 배열을 사용하여 인스턴스화 할 수 있습니다. 배열의 값은 모델에서 상응하는 속성들과 같은 것이어야 합니다.

중첩 객체

객체가 속성이 RLMObjects 또는 RLMArrays 일 경우에, Dictionary 및/또는 중첩 배열을 사용하여 재귀적으로 설정할 수 있습니다. 단순하게 각 객체와 속성을 대표하는 Dictionary 또는 배열을 간단하게 대체할 수 있습니다:

// Instead of using already existing dogs...
Person *person1 = [[Person alloc] initWithValue:@[@"Jane", @30, @[aDog, anotherDog]]];

// ...we can create them inline
Person *person2 = [[Person alloc] initWithValue:@[@"Jane", @30, @[@[@"Buster", @5],
                                                                  @[@"Buddy", @6]]]];

이는 중첩 배열 및 Dictionary의 조합을 위해 작동할 것 입니다. RLMArrayRLMObject만 포함하고, NSString과 같은 기본 유형을 포함할 수 없습니다.

객체 추가

다음과 같은 방법으로 Realm에 객체를 추가할 수 있습니다:

// Create object
Person *author = [[Person alloc] init];
author.name    = @"David Foster Wallace";

// Get the default Realm
RLMRealm *realm = [RLMRealm defaultRealm];
// You only need to do this once (per thread)

// Add to Realm with transaction
[realm beginWriteTransaction];
[realm addObject:author];
[realm commitWriteTransaction];

Realm에서 객체를 생성한 후에는 그 객체는 지속적으로 사용할 수 있습니다. 그리고 그것을 구성하는 모든 변경사항도 지속적으로 사용가능합니다. (반드시 쓰기 트랜잭션 내에서 이루어져야 합니다.) 쓰기 트랜잭션이 커밋되는 시점에 같은 Realm 객체를 사용하는 서로 다른 스레드에 변경사항이 반영됩니다.

이 점은 주의하시기 바랍니다. 동시에 각각의 블록에 여러 쓰기가 진행 중이라면 해당 스레드는 차단됩니다. 이 부분은 여러 다른 범용 솔루션과 비슷합니다. 그래서 이런 경우에는 적절한 사례를 살펴보길 추천합니다. 즉, 쓰기에 대해서 스레드를 나누는 것을 말합니다.

Realm의 MVCC 설계 덕분에 쓰기 트랜잭션이 실행 중에는 읽기는 제한되지 않습니다. 한 번에 여러 스레드에서 동시 쓰기를 해야하지 않는 이상, 대량의 쓰기 트렌잭션으로 구성하기 보다는 다수의 세세한 쓰기 트랜잭션으로 구성하길 선호하실 겁니다. Realm에 쓰기 트랜잭션을 실행할 경우 해당 Realm의 다른 모든 객체에게 통지가 가며 자동 업데이트됩니다.

자세한 내용은 RLMRealmRLMObject를 확인하세요.

객체 업데이트

Realm이 객체를 업데이트할 몇 가지 방법 모두 상황에 따라 서로 다른 트레이드 오프를 제공합니다. 상황에 가장 적합한 하나를 선택합니다:

입력 업데이트

쓰기 트랜잭션 내에서 속성을 설정하여 객체를 업데이트 할 수 있습니다.

// Update an object with a transaction
[realm beginWriteTransaction];
author.name = @"Thomas Pynchon";
[realm commitWriteTransaction];

기본키를 통한 객체 생성 및 업데이트

모델에 기본키가 있다면, -[RLMRealm addOrUpdateObject:]를 사용해서 기본키 값을 통해 Realm의 편리한 업데이트나 객체 추가 기능을 사용할 수 있습니다.

// Creating a book with the same primary key as a previously saved book
Book *cheeseBook = [[Book alloc] init];
cheeseBook.title = @"Cheese recipes";
cheeseBook.price = @9000;
cheeseBook.id = @1;

// Updating book with id = 1
[realm beginWriteTransaction];
[realm addOrUpdateObject:cheeseBook];
[realm commitWriteTransaction];

만약 데이터베이스에 기본키가 ‘1’인 Book 객체가 이미 존재한다면, 해당 객체가 설정 값으로 업데이트됩니다. 만약 해당 객체가 없다면 새 Book 객체를 생성하고 데이터베이스에 추가합니다.

또한 기본키와 함께 업데이트를 원하는 값의 일부만을 전달하여 기본키와 함께 객체를 업데이트할 수 있습니다:

// Assuming a "Book" with a primary key of `1` already exists.
[realm beginWriteTransaction];
[Book createOrUpdateInRealm:realm withValue:@{@"id": @1, @"price": @9000.0f}];
// the book's `title` property will remain unchanged.
[realm commitWriteTransaction];

객체 업데이트를 할 경우 NSNulloptional 속성의 올바른 값으로 간주됩니다. NSNull 속성 값과 함께 딕셔너리를 사용할 경우 객체에 해당 값이 적용되고 해당 속성이 빈 속성이 됩니다. 계획 밖의 데이터 손실을 막기 위해서는 상기 메서드 사용 업데이트 시 업데이트가 필요한 속성만을 넣으세요.

Key-Value Coding (KVC)

RLMObject, RLMResult, RLMArray 모두 key-value coding(KVC)을 준수합니다. 런타임시 업데이트 할 수 있는 속성을 결정해야 할 때 유용할 수 있습니다.

컬렉션에 KVC를 적용하는 것은 모든 아이텐에 대한 접근을 작성하는 동안 컬렉션을 통해 반복하는 오버 헤드 없이 대량으로 객체를 업데이트 할 수 있는 좋은 방법입니다.

RLMResults<Person *> *persons = [Person allObjects];
[[RLMRealm defaultRealm] transactionWithBlock:^{
  [[persons firstObject] setValue:@YES forKeyPath:@"isFirst"];
  // set each person's planet property to "Earth"
  [persons setValue:@"Earth" forKeyPath:@"planet"];
}];

객체 삭제

삭제할 객체를 쓰기 트랜젝션 내에서 -[RLMRealm deleteObject:] 메서드에 전달하세요.

// cheeseBook stored in Realm

// Delete an object with a transaction
[realm beginWriteTransaction];
[realm deleteObject:cheeseBook];
[realm commitWriteTransaction];

또한 Realm에 저장된 모든 객체를 삭제할 수 있습니다. Realm 파일이 효율적으로 이후 객체를 위해 그 공간으로 재사용할 디스크 사이즈를 유지할 것을 명심하세요.

// Delete all objects from the realm
[realm beginWriteTransaction];
[realm deleteAllObjects];
[realm commitWriteTransaction];

쿼리

쿼리는 RLMObject 콜렉션을 포함하는 RLMResults 인스턴스를 반환합니다. RLMResultsNSArray와 매우 유사한 인터페이스를 제공하고 RLMResults에 포함된 객체는 인덱스 첨자로 접근할 수 있습니다. NSArray와 달리, RLMResults는 타입을 강제하고 하나의 RLMObject 서브 클래스만을 갖도록 설계되었습니다.

모든 쿼리 (쿼리와 속성 접근을 포함하여)는 바로 처리되지 않습니다. 속성에 접근할 때에만 데이터를 읽습니다.

쿼리에 대한 결과는 데이터의 복사본이 아닙니다: 쿼리 결과의 수정(쓰기 트랜잭션과 함께)은 직접적으로 디스크에 반영합니다. 동일하게, RLMResults 내에 있는 RLMObject로부터 직접적으로 [관계]((#relationships)그래프를 탐색할 수 있습니다.

쿼리 실행은 결과가 사용되지 전까지 지연됩니다. 즉 여러 임시 RLMResults를 연쇄적으로 사용해서 데이터의 소팅과 필터링을 해도 중간 단계 처리를 위한 추가 작업이 수행되지 않습니다. 한번 쿼리가 실행되거나 알림 블록이 추가되면, RLMResults가 Realm에서 발생된 변화에 맞춰 최신 상태로 바뀌며, 가능시 쿼리 실행이 백그라운드이 스레드에서 수행됩니다.

Realm에서 가장 기본적인 객체 검색 메소드는 +[RLMObject allObjects]이고, 이는 기본 Realm으로부터 같은 서브 클래스 타입의 모든 RLMObject 인스턴스의 RLMResults를 반환합니다.

RLMResults<Dog *> *dogs = [Dog allObjects]; // retrieves all Dogs from the default Realm

필터링

만약에 이미 NSPredicate에 익숙하다면, Realm에서 조회하는 방법을 이미 알고 있습니다. RLMObjects, RLMRealm, RLMArray, RLMResults 모두 NSArray를 조회했던 것과 같이 간단하게 NSPredicate 인스턴스, 조건 문자열이나 조건 형식의 문자열 전달로 특정 RLMObject 인스턴스를 조회할 수 있는 메소드를 제공합니다.

예를 들면, 아래는 이전의 예시로부터 확장하여 [RLMObject objectsWhere:]를 호출하여, 기본 Realm으로 부터 색이 ‘tan’이고 이름이 ‘B’로 시작하는 황갈색의 dog를 검색할 수 있습니다:

// Query using a predicate string
RLMResults<Dog *> *tanDogs = [Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"];

// Query using an NSPredicate
NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@",
                                                     @"tan", @"B"];
tanDogs = [Dog objectsWithPredicate:pred];

Apple의 Predicates Programming Guide에서 조건절에 대한 상세한 정보를 얻을 수 있고 우리의 NSPredicate Cheatsheet를 사용할 수 있습니다. Realm은 일반적인 조건절을 지원합니다:

  • 비교 연산자는 속성 이름 또는 피연산자가 될 수 있습니다. 피연산자 중 적어도 하나는 속성의 이름이어야 합니다.
  • 비교 연산자 ==, <=, <, >=, >, !=, BETWEEN은 int, long, long long, float, double, NSDate 속성타입을 지원합니다.

    예. age == 45

  • 비교 연산자 ==, !=,

    예. [Employee objectsWhere:@"company == %@", company]

  • 비교 연산자 ==, !=는 boolean 속성을 지원합니다.

  • NSString, NSData 속성을 위해서 ==, !=, BEGINSWITH, CONTAINS, ENDSWITH 연산자를 지원합니다.

    예. name CONTAINS ‘Ja’

  • NSString 속성에서 LIKE 연산자는 좌측 프로퍼티와 우측 표현을 비교합니다. ?*를 와일드카드 캐릭터로 사용할 수 있는데, ?는 하나의 캐릭터를, *는 0개 이상의 캐릭터를 나타냅니다.

    예. value LIKE '?bc*'는 “abcde”와 “cbc”와 같은 캐릭터를 매치합니다.

  • 대소문자를 구분하지 않는 string 비교.

    예. name CONTAINS[c] ‘Ja’ 알림. “A-Z”, “a-z”만이 대소문자 구분에서 제외됩니다. [d] 변경자와 함께 사용할 수 있습니다.

  • 발음 구별 부호를 구분하지 않는 비교. 예. name BEGINSWITH[d] ‘e’étoile 와 매칭됩니다. [c] 변경자와 함께 사용할 수 있습니다.

  • Realm은 다음의 연산자 또한 지원합니다: “AND”, “OR”, “NOT”.

    예. name BEGINSWITH ‘J’ AND age >= 32

  • 포함 연산자 IN.

    예. name IN {‘Lisa’, ‘Spike’, ‘Hachi’}

  • Nil 비교 ==, !=.

    예. [Company objectsWhere:@"ceo == nil"].

  • Nil 비교 ==, !=

    예. [Company objectsWhere:@"ceo == nil"].

    nil 자체가 같은 SQL과 다르게 값이 없는 것보다 nil처럼 특별한 값으로 지정합니다.

  • 객체를 대상으로한 관계에서만 유효합니다. 이 예에서는 ceo은 Company에서 속성으로 정의되어 있습니다.

  • ANY 비교.

    예. ANY student.age < 21

  • 집계 표현 타입 @count, @min, @max, @sum@avg 등을 RLMArray와 RLMResults` 속성에서 지원합니다.

    예. [Company objectsWhere:@"employees.@count > 5"]라 쓰면, 5명 이상의 직원을 가진 모든 회사를 찾을 수 있습니다.

  • 다음 제약 사항 하에서 서브 쿼리가 지원됩니다.
    • SUBQUERY 표현에 적용되는 유일한 오퍼레이터인 @count 사용할 경우
    • SUBQUERY(…).@count 표현은 반드시 상수와 비교돼야 합니다.
    • 서로 관련 있는 서브 쿼리는 아직 지원되지 않습니다.

좀 더 상세한 내용은 [RLMObject objectsWhere:]을 통해 확인하세요.

정렬

RLMResults는 키 경로나, 속성, 혹은 한 개 이상의 정렬 기술자(sort discriptor)로 정렬 기준이나 순서를 설정하도록 지원합니다. 예를 들어, 아래는 결과값을 Dog의 이름 알파벳순으로 정렬하는 코드입니다.

// Sort tan dogs with names starting with "B" by name
RLMResults<Dog *> *sortedDogs = [[Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"]
                                      sortedResultsUsingKeyPath:@"name" ascending:YES];

일대일 관계의 속성을 키 경로로 사용할 수 있습니다.

RLMResults<Person *> *dogOwners = [Person allObjects];
RLMResults<Person *> *ownersByDogAge = [dogOwners sortedResultsUsingKeyPath:@"dog.age" ascending:YES];

sortedResultsUsingKeyPath:sortedResultsUsingProperty:는 정렬 기준으로 여러 속성을 지원하지 않으므로 체인으로 연결할 수 없으며, 마지막 sortedResults... 호출만 사용됩니다. 여러 개의 속성으로 정렬하려면 sortedResultsUsingDescriptors:를 여러 개의 RLMSortDescriptor와 함께 사용하세요.

더 자세한 설명은 다음을 참조하세요.

Results의 순서는 쿼리가 소팅된 경우에만 일관성 유지를 보장함을 기억하세요. 성능 상의 이유로 삽입 순서는 보장되지 않습니다. 삽입 순서를 유지해야 한다면 참고 문서의 솔루션을 참조하세요.

연쇄(Chaing)

Realm이 내세우는 조회 엔진의 장점은 각각의 연속적인 쿼리에 대한 데이터베이스 서버에 별도의 연결을 요구하는 기존의 데이터베이스와 비교하여 매우 적은 오버헤드로 연속 조회하는 기능입니다.

예를 들어, 만약에 ‘tan’색의 dog를 조회한 후, ‘tan’색의 dog 중에서 이름이 ‘B’로 시작하는 dog를 찾기 원할 때 아래와 같이 연결된 쿼리로 조회가 가능합니다:

RLMResults<Dog *> *tanDogs = [Dog objectsWhere:@"color = 'tan'"];
RLMResults<Dog *> *tanDogsWithBNames = [tanDogs objectsWhere:@"name BEGINSWITH 'B'"];

Results 자동 업데이트

RLMResults는 실시간으로 기본 데이터가 자동으로 뷰에 업데이트됩니다. 이는 Results가 다시 패치가 될 필요가 없다는 의미입니다. Results는 현재 스레드의 쓰기 트랜잭션을 포함한 현재 스레드의 Realm의 현재 상태를 항상 반영합니다. 단 한 가지 예외는 열거 시작 후 쿼리에 맞는 객체를 열거하는 for...in으로, 열거 중 삭제나 수정이 일어나도 필터에 의해 제외됩니다.

RLMResults<Dog *> *puppies = [Dog objectsInRealm:realm where:@"age < 2"];
puppies.count; // => 0

[realm transactionWithBlock:^{
  [Dog createInRealm:realm withValue:@{@"name": @"Fido", @"age": @1}];
}];

puppies.count; // => 1

필터링과 연속된 모든 객체는 모두 RLMResults에 적용됩니다.

RLMResults는 Realm이 빠르고 효율적이게 유지하는 것에 더불어 코드를 더 간단하고 더 반응하게 합니다. 예를 들면, 만약에 뷰 컨트롤러가 쿼리의 결과에 의존한다면, 사용자가 RLMResults에 저장할 수 있으며, 각각에 접근하기 전에 데이터를 새로 고칠 수 있는지 확인하지 않고도 접근할 수 있습니다.

Realm 알림을 구독하면 Realm 데이터가 업데이트 될때 RLMResults를 다시 가지고 올 필요 없이 UI 갱신 시점을 알 수 있습니다.

경과가 자동으로 업데이트 되기 때문에 일정한 인덱스와 개수를 유지하는 것이 중요합니다. RLMResults가 고정되어 있는 유일한 시간은 그 위에 빠르게 열거할 때, 열거하면서 쿼리에 일치하는 객체가 변형된 경우입니다.

[realm beginWriteTransaction];
for (Person *person in [Person objectsInRealm:realm where:@"age == 10"]) {
  person.age++;
}
[realm commitWriteTransaction];

RLMResults에서 작업을 수행하기 위해 key-value coding을 사용할 수도 있습니다.

Results 제한

대부분의 다른 데이터베이스는 쿼리(예. SQLite의 ‘LIMIT’ 키워드)를 통해 페이지를 매긴 결과를 제공합니다. 디스크에서 너무 많은 정보를 읽어오지 않도록, 혹은 메모리에 너무 많은 결과를 넣지 않도록 방지하기 위한 기능입니다.

한편 Realm은 한 번 명시적으로 접근한 쿼리의 결과로부터 객체를 로드함에 따라 Realm의 쿼리는 지연되므로 이런 페이지화를 수행할 필요가 없습니다.

UI 관련 등의 구현적인 이유로 쿼리로부터 얻은 객체의 특정한 부분집합이 필요한 경우, RLMResults를 얻는 것처럼 간단한 방법으로 필요한 객체만을 읽어올 수 있습니다.

// Loop through the first 5 Dog objects
// restricting the number of objects read from disk
RLMResults<Dog *> *dogs = [Dog allObjects];
for (NSInteger i = 0; i < 5; i++) {
  Dog *dog = dogs[i];
  // ...
}

Realm

기본 Realm

[RLMRealm defaultRealm]을 호출하여 realm 변수를 초기화하여 접근합니다. 해당 메소드는 앱 내부 Documents 폴더 안에 “default.realm”에 대응되는 RLMRealm 객체를 반환합니다.

많은 Realm API 메소드는 RLMRealm 객체를 받는 버전과 기본 Realm을 사용하는 간편한 버전이 있습니다. 예를 들어, [RLMObject allObjects][RLMObject allObjectsInRealm:[RLMRealm defaultRealm]]는 동일합니다.

Realm 설정

Realm 파일이 저장된 위치와 같은 설정은 RLMRealmConfiguration을 통해 이루어집니다. 그 설정은 Realm 인스턴스가 필요할 때 마다 [RLMRealm realmWithConfiguration:config error:&err]에 전달 될 수 있습니다. 또는 [RLMRealmConfiguration setDefaultConfiguration:config]와 같이 기본 Realm 에 사용하는 것을 설정할 수 있습니다.

예를 들어, 사용자들이 웹 백엔드에 로그인을 할 수 있는 앱을 가졌다고 가정하면, 계정 전환을 신속하게 지원하는 것이 좋습니다. 다음과 같은 코드로 기본 Realm을 사용할 자신의 Realm 파일에 각각의 계정을 줄 수 있습니다:

@implementation SomeClass
+ (void)setDefaultRealmForUser:(NSString *)username {
  RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];

  // Use the default directory, but replace the filename with the username
  config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]
                      URLByAppendingPathComponent:username]
                      URLByAppendingPathExtension:@"realm"];

  // Set this as the configuration used for the default Realm
  [RLMRealmConfiguration setDefaultConfiguration:config];
}
@end

다른 Realm

간혹 여러 개의 Realm을 사용할 필요가 있습니다. 애플리케이션과 함께 일부 데이터를 메인 Realm뿐만 아니라 Realm 파일에 모아놓을 필요가 있는 경우 다음과 같은 코드를 사용할 수 있습니다:

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];

// Get the URL to the bundled file
config.fileURL = [[NSBundle mainBundle] URLForResource:@"MyBundledData" withExtension:@"realm"];
// Open the file in read-only mode as application bundles are not writeable
config.readOnly = YES;

// Open the Realm with the configuration
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];

// Read some data from the bundled Realm
RLMResults<Dog *> *dogs = [Dog objectsInRealm:realm where:@"age > 5"];

지정 URL이 Realm을 초기화하는데 사용되는 경우에, 그것은 쓰기 권한이 있는 위치에 있어야 한다는 것을 주의하세요. Realm 파일을 저장하기에 가장 일반적인 경로는 iOS의 경우 “Documents”이며 OSX의 경우 “Application Support” 입니다. 다시 생성될 수 있는 파일은 <Application_Home>/Library/Caches 경로를 추천합니다. Apple iOS Data Storage 가이드라인을 따르세요.

In-Memory Realm

Realm은 기본적으로 디스크에 유지됩니다. 그러나 RLMRealmConfiguration에서 경로보다 inMemoryIdentifier에 설정하면, 순수하게 메모리에서 작동할 수 있습니다.

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.inMemoryIdentifier = @"MyInMemoryRealm";
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];

In-memory Realm은 영구적으로 데이터를 저장하지 않습니다. 하지만 조회, 관계, 스레드 보호를 포함한 Realm의 모든 기능이 동일하게 동작합니다. 디스크 영구보존을 위한 오버헤드를 피하려 한다면 유용한 옵션입니다.

In-memory Realm은 프로세스 간 알림 같은 것들을 조정하기 위해 임시 디렉터리에 여러 파일들을 만듭니다. 운영 체제가 메모리에 의한 압력으로 디스크를 교체할 필요가 없는 한 실제 어떠한 데이터도 파일에 기록되지 않습니다.

주의: In-memory Realm의 대한 참조가 범위를 벗어나게 되면 모든 데이터는 메모리 해제됩니다. 앱의 생명 주기 동안 In-memory Realm에 메모리 참조를 하도록 추천합니다. (On-disk Realm에는 해당되지 않습니다.)

에러 핸들링

일반적인 disk IO 처리와 마찬가지로, RLMRealm 인스턴스 생성은 자원이 부족한 환경에서는 실패할 수 있습니다. 실제로 각 스레드에서 처음 Realm 인스턴스를 만들려고 할 때 에러가 발생할 수 있습니다. 그 이후 접근에서는 스레드마다 캐시된 인스턴스가 반환되기 때문에 실패하는 것은 아닙니다.

에러를 처리하고 복구하려면 NSError 포인터를 error 매개 변수로 전달합니다.

NSError *error = nil;

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
if (!realm) {
  // handle error
}

비동기적으로 Realm 열기

마이그레이션, 압축, 동기 Realm에서 원격 컨텐츠 다운로드 등 Realm을 여는데 시간이 많이 걸리는 작업을 하는 경우 asyncOpen API를 사용해서 지정된 큐에 디스패치하기 전에 백그라운드 스레드에서 Realm을 사용가능한 상태로 만드는데 필요한 작업을 모두 수행하는 것이 좋습니다.

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 1;
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  // potentially lengthy data migration
};
[RLMRealm asyncOpenWithConfiguration:config
                       callbackQueue:dispatch_get_main_queue()
                            callback:^(RLMRealm *realm, NSError *error) {
  if (realm) {
    // Realm successfully opened, with migration applied on background thread
  } else if (error) {
    // Handle error that occurred while opening the Realm
  }
}];

또한, 다음처럼 동기 Realm은 모든 원격 컨텐츠가 다운로드되고 로컬에서 사용가능할 때까지 기다립니다.

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:user realmURL:realmURL];
[RLMRealm asyncOpenWithConfiguration:config
                       callbackQueue:dispatch_get_main_queue()
                            callback:^(RLMRealm *realm, NSError *error) {
  if (realm) {
    // Realm successfully opened, with all remote data available
  } else if (error) {
    // Handle error that occurred while opening or downloading the contents of the Realm
  }
}];

Realm 간의 객체 복사

Realm에 저장된 객체를 다른 Realm에 복사하려면 +[RLMObject createInRealm:withValue:] 메소드에 원본 객체를 인수로 전달합니다. 예를 들면. [MyRLMObjectSubclass createInRealm:otherRealm withValue:originalObjectInstance]와 같이 사용합니다.

Realm 객체는 처음 만들어진 스레드에서 접근할 수 있기 때문에 이 복사본은 동일한 스레드에서 Realm이 작동된다는 것을 기억하세요.

+[RLMObject createInRealm:withValue:]는 싸이클이 있는 객체 그래프 처리를 지원하지 않습니다. 부모를 참조하는 객체가 포함된 관계가 있는 객체를 직, 간접적으로전달하지 마세요.

Realm File을 찾는 방법

여러분의 앱에 있는 Realm 파일의 위치를 모르는 경우에는 StackOverflow 답변을 참고하세요.

보조 Realm File

표준 .realm 파일 외에도, Realm은 내부 오퍼레이션을 위해 추가 파일들과 디렉터리들을 생성하고 유지합니다.

  • .realm.lock - 자원 락을 위한 락 파일
  • .realm.management - Directory of interprocess lock files. - 프로세스간 락 파일을 위한 디렉터리
  • .realm.note - 알림을 위한 named pipe

이들 파일은 .realm 데이터베이스 파일에 아무 영향을 미치지 않으며, 부모 데이터베이스 파일이 지워지거나 변경돼도 에러를 일으키지 않습니다.

이슈 보고하기의 경우 메인 .realm 파일과 함께 이들 보조 파일을 보내주시면 내부의 디버깅 정보를 파악할 수 있어 도움이 됩니다.

Realm을 앱에 번들링

처음 부팅을 빠르게 하기 위해서 같은 앱에 초기 데이터를 통합하는 것이 많습니다. 다음은 Realm을 초기 데이터로 번들하는 예입니다.

  1. 먼저 초기 데이터가 들어있는 Realm을 제공합니다. 출시 때와 같은 데이터 모델을 정의하고 데이터를 넣습니다. Realm 파일은 크로스 플랫폼에서 사용할 수 있기 때문에 데이터의 작성은 OS X와 iOS 시뮬레이터에서 해도 문제 없습니다. (JSONImport example를 참고하세요.)

  2. 초기 데이터가 포함됨 코드의 끝부분에 Realm 파일을 복사하는 방법을 사용하고, Realm 파일 크기를 최적화하세요. (-[RLMRealm writeCopyToPath:error:]를 참고) 이 메소드를 사용하여 Realm 파일을 복사하면 Realm 파일 크기를 줄일 수 있고 결국엔 앱의 크기가 가벼워지기 때문에 사용자가 더 빠르게 다운로드 할 수 있습니다.

  3. 복사된 Realm 파일을 Xcode 프로젝트 내비게이터에 드래그 앤 드롭을 합니다.
  4. 프로젝트 설정 Build Phase 탭에서 “Copy Bundle Resources”에 Realm 파일을 추가합니다.
  5. 이 시점에서 추가한 Realm 파일에 앱에서 접근할 수 있습니다. [[NSBundle mainBundle] pathForResource:ofType:] 메소드를 사용하여 파일 경로를 가지고 옵니다.
  6. 동봉한 Realm 데이터 파일이 고정 데이터만을 포함하여 변경할 필요가 없는 것이면, RLMRealmConfiguration에서 readOnly = true를 지정하여 읽기 전용으로 직접 번들 내의 파일을 열 수 있습니다. 반면에 초기 데이터를 변경할 필요가 있다면, 번들에서 문서 디렉터리 등에 Realm 파일을 [[NSFileManager defaultManager] copyItemAtPath:toPath:error:] 메소드로 복사하고 사용할 수 있습니다.

Realm 파일을 초기 데이터로 통합한 예로 migration sample app를 참고하세요.

클래스 부분집합

각 Realm 파일에 저장되는 모델의 정의를 구분하고 싶은 경우를 가정해 보겠습니다.

에를 들어서 내부에 Realm을 사용하는 다른 구성 요소를 두 팀이 개발하고 있다면, 그들 간에 마이그레이션을 조정하고 싶지 않을 것입니다. 아래와 같이, RLMRealmConfigurationobjectClasses을 이용해 각각의 Realm에 저장하는 모델 클래스를 제한할 수 있습니다:

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.objectClasses = @[MyClass.class, MyOtherClass.class];
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];

Realms 압축

Realm 아키텍처에서 파일 크기는 항상 최신 데이터 상태보다 큽니다. 어떻게 이 아키텍처로 Realm의 뛰어난 성능과 동시성, 안전 상의 이점을 얻을 수 있는지는 스레드 문서를 참조하세요.

또한, 파일 크기를 확장하는 고비용의 시스템 호출을 줄이기 위해 Realm 파일은 일반적으로 런타임에 크기가 줄어들지 않고 파일 내에서 추적되는 여유 공간 내에 새 데이터를 기록합니다.

그러나 Realm 파일의 상당 부분이 여유 공간으로 구성되는 경우가 있습니다. 따라서 이번 릴리즈는 Realm의 설정 객체에 shouldCompactOnLaunch 블럭 속성을 추가해서 Realm을 반환하기 전에 압축할지 결정하는 기능을 더했습니다.

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.shouldCompactOnLaunch = ^BOOL(NSUInteger totalBytes, NSUInteger usedBytes){
  // totalBytes refers to the size of the file on disk in bytes (data + free space)
  // usedBytes refers to the number of bytes used by data in the file

  // Compact if the file is over 100MB in size and less than 50% 'used'
  NSUInteger oneHundredMB = 100 * 1024 * 1024;
  return (totalBytes > oneHundredMB) && (usedBytes / totalBytes) < 0.5;
};

NSError *error = nil;
// Realm is compacted on the first open if the configuration block conditions were met.
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
if (error) {
  // handle error compacting or opening Realm
}

압축 작업은 Realm 파일 전체 내용을 읽고 다른 위치의 새 파일로 작성한 다음 원본 파일을 교체합니다. 파일의 데이터 양에 따라 고비용의 작업이 될 수 있습니다.

압축의 수행 횟수와 Realm 파일 크기 사이의 균형점을 파악하기 위해 여러 번 시험해 보기를 권장합니다.

마지막으로 다른 프로세스가 Realm에 접근하는 경우 설정 블럭의 조건이 충족되더라도 압축이 생략됩니다. Realm에 접근하는 동안에는 압축이 안전하게 수행될 수 없기 때문입니다.

동기 Realm에서는 shouldCompactOnLaunch 블럭 설정이 지원되지 않습니다. 압축은 동기화를 위해 보존돼야 하는 트랜잭션 로그를 보존하지 않기 때문입니다.

Realm 파일 삭제

캐시 청소나 전체 데이터셋 리셋 등 몇몇 경우 디스크로부터 전체 Realm 파일을 지우는 것이 필요할 경우가 있습니다.

Realm은 꼭 필요한 경우를 제외하고는 데이터를 메모리에 복사하지 않으므로, Realm에 의해 관리되는 모든 객체는 디스크의 파일에 대한 참조를 포함합니다. 따라서 파일을 안전하게 삭제하기 전에 할당을 취소해야 합니다. 여기에는 Realm에서 읽거나 Realm에 추가된 모든 객체가 포함됩니다. 모든 RLMRealm 자체를 포함해서, RLMArray, RLMResults,와 RLMThreadSafeReference 객체 모두가 해당됩니다.

즉, 실제로 사용할 때 Realm 파일을 지우려면 Realm을 열기 전인 애플리케이션 시작 때나, 모든 Realm 객체의 할당이 해제될 수 있도록 명시적인 autorelease pool 내에서만 Realm을 여는 경우여야 합니다.

마지막으로 엄격히 규제되는 사항은 아니지만 메인 Realm 파일과 함께 보조 Realm 파일을 지워서 관련 파일을 모두 삭제하는 것이 좋습니다.

@autoreleasepool {
  // all Realm usage here
}
NSFileManager *manager = [NSFileManager defaultManager];
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
NSArray<NSURL *> *realmFileURLs = @[
  config.fileURL,
  [config.fileURL URLByAppendingPathExtension:@"lock"],
  [config.fileURL URLByAppendingPathExtension:@"note"],
  [config.fileURL URLByAppendingPathExtension:@"management"]
];
for (NSURL *URL in realmFileURLs) {
  NSError *error = nil;
  [manager removeItemAtURL:URL error:&error];
  if (error) {
    // handle error
  }
}

앱의 백그라운드 새로고침과 함께 Realm 사용하기

iOS 8 이상에서 앱 내부 파일은 기기가 잠길 때마다 NSFileProtection를 사용해서 자동으로 암호화됩니다. 만약 앱이 기기가 잠겨있는 사이 Realm과 관련된 어떤 작업을 시도했는데 Realm 파일의 속성이 기본값인 암호화로 맞춰 있다면 open() failed: Operation not permitted 예외가 발생합니다.

이를 방지하기 위해서는 Realm 파일과 보조 파일의 파일 보호 속성이 디바이스 잠금 상태에서도 파일 접근을 허용하는 NSFileProtectionCompleteUntilFirstUserAuthentication와 같은 덜 엄격한 모드여야 합니다.

이 방식으로 전체 iOS 파일의 암호화를 하지 않기를 원한다면 Realm의 자체 암호화를 사용해서 데이터를 보호하는 것을 권장합니다.

때때로 보조 파일의 생성이 지연되고 오퍼레이션 중간에 삭제되므로, 이들 Realm 파일을 담는 상위 폴더에 파일 보호 속성을 적용하는 것이 좋습니다. 이 경우 생성 시간과 상관없이 모든 관련 Realm 파일에 속성이 올바르게 적용될 수 있습니다.

RLMRealm *realm = [RLMRealm defaultRealm];

// Get our Realm file's parent directory
NSString *folderPath = realm.configuration.fileURL.URLByDeletingLastPathComponent.path;

// Disable file protection for this directory
[[NSFileManager defaultManager] setAttributes:@{NSFileProtectionKey: NSFileProtectionNone}
                                 ofItemAtPath:folderPath error:nil];

스레드

Realm 쓰기 트랜잭션의 생명 주기는RLMRealm 인스턴스의 메모리 수명에 연결됩니다. Realm의 자동 새로 고침을 사용하고 모든 Realm API의 사용을 백그라운드 스레드의 명시적 오토릴리즈 풀로 감싸서 예전 Realm 트랜잭션이 고정되는 것을 피하세요.

이 효과에 대한 자세한 내용은 현재 제한 사항 섹션을 참고하세요.

별도의 스레드에서 Realm을 사용하고 있으면, Realm 모든 객체는 일반적인 객체처럼 동시성과 멀티 스레딩에 대한 걱정없이 처리할 수 있습니다. Realm(다른 스레드에 동시에 업데이트 된다고 해도)에 접근하기 위해 잠금이나 단독적으로 처리를 생각할 필요가 없습니다.

Realm은 동시 처리를 쉽게 처리 할 수 있도록 하기 위해서 각 스레드에서 항상 일관성있게 데이터를 반환합니다. 많은 스레드에서 Realm을 동시에 조작한다면, 각각의 스레드마다 스냅 샷 데이터를 반환하고 일관성이 없는 상태가 될 수 있습니다.

한가지 주의해야 할 것은 여러 스레드를 걸처 같은 Realm 인스턴스를 공유할 수 없다는 것입니다. 만약에 여러 스레드에서 동일한 객체에 접근할 필요가 있는 경우에는 각각의 스레드가 Realm 인스턴스를 지정해야합니다. (그렇지 않으면 데이터가 일관적이지 않게 보입니다.)

다른 스레드에서 변경된 데이터 보기

메인 UI 스레드(또는 Runloop의 스레드) 객체는 runloop가 돌 때마다 자동으로 다른 스레드에서 변경된 데이터가 반영됩니다. 아무때나 그 시점의 스냅 샷 데이터를 반환하고 다른 스레드에서 데이터가 변경되었는지를 걱정할 필요없이 항상 일관된 데이터를 볼 수 있습니다.

스레드에서 Realm 파일을 열 때, Realm의 상태는 마지막 커밋이 성공한 상태입니다. 그리고 다음 업데이트가 반영될 때 까지 그대로입니다. Realm은 autorefreshNO인 경구가 아니라면 런루프가 돌아올때마다 자동으로 최신 데이터로 업데이트됩니다. 만약에 스레드가 런루프를 가지고 있지 않은 일반적인 백그라운드 스레드인 경우에는 최신의 데이터를 반영하기 위해서 [-[RLMRealm refresh] 메소드를 직접 호출해야 합니다.

또한 트랜잭션이 커밋된 경우에도 최신 데이터가 반영이 됩니다. ([-[RLMRealm commitWriteTransaction]).

주기적으로 최신 데이터 반영이 실패면 트랜잭션이 “pinned”되어 디스크 공간의 재사용을 방해합니다. 그것은 파일 크기가 커지는 것을 초래할 수 있습니다. 이 현상에 대한 자세한 내용은 현재 버전의 제한 사항을 참조하세요.

스레드 간의 객체 전달

Realm이 관리하지 않는 RLMObject의 인스턴스는 일반적으로 NSObject의 서브 클래스가 되므로 스레드 전반에 객체를 전달해도 문제 없습니다.

RLMRealm, RLMResults, RLMArray의 인스턴스나, Realm이 관리하는 RLMObject 인스턴스는 스레드에 국한되므로 생성된 스레드 내에서만 사용할 수 있으며, 다른 스레드에서 사용되면 예외가 발생합니다. 이는 Realm 트랜잭션 버전을 강제로 분리하기 위한 방법으로, 객체가 다른 트랜잭션에서 스레드 사이에 전달될 때 잠재적으로 광범위한 관계 그래프를 사용하지 않고 이 정보들을 제어하는 것은 불가능하기 때문입니다.

다음 방법을 사용하면 스레드에 국한된 Realm 인스턴스를 안전하게 전달할 수 있습니다.

  1. 스레드 제한 객체를 RLMThreadSafeReference로 초기화합니다.
  2. 원하는 스레드나 큐로 해당 RLMThreadSafeReference를 전달합니다.
  3. 이 참조를 목적지 Realm에서 -[RLMRealm resolveThreadSafeReference:] 호출을 통해 처리한 후, 반환된 객체를 일반 사용법과 동일하게 사용합니다.

아래 코드 예제를 참고하세요.

Person *person = [Person new];
person.name = @"Jane";
[realm transactionWithBlock:^{
  [realm addObject:person];
}];
RLMThreadSafeReference *personRef = [RLMThreadSafeReference
  referenceWithThreadConfined:person];

dispatch_async(queue, ^{
  @autoreleasepool {
    RLMRealm *realm = [RLMRealm realmWithConfiguration:realm.configuration
                                                   error:nil];
    Person *person = [realm resolveThreadSafeReference:personRef];
    if (!person) {
      return; // person was deleted
    }
    [realm transactionWithBlock:^{
      person.name = @"Jane Doe";
    }];
  }
});

RLMThreadSafeReference 객체는 한 번만 처리돼야 합니다. RLMThreadSafeReference를 처리하지 못하면 Realm의 소스 버전은 참조가 할당 해제될 때까지 고정됩니다. 따라서 RLMThreadSafeReference은 짧은 시간 내에 사용해야 합니다.

* 아래 타입의 몇몇 속성과 메서드는 어느 스레드에서나 접근할 수 있습니다.

  • RLMRealm: 모든 속성, 클래스 메서드, initializers
  • RLMObject: isInvalidated, objectSchema, realm, class methods, 과 initializers.
  • RLMResults: objectClassNamerealm.
  • RLMArray: isInvalidated, objectClassName, 과 realm.

스레드 간 Realm 공유

여러 스레드에서 동일한 Realm 파일에 접근하기 위해서는 스레드마다 다른 Realm 객체를 두어야 하고 새로운 Realm를 초기화해야 합니다. 같은 경로를 지정할 경우에는, 모든 RLMRealm 객체는 디스크상의 같은 파일에 대응됩니다.

스레드 간에 Realm 인스턴스 공유를 지원하지 않습니다. 같은 Realm 파일에 접근하는 Realm 인스턴스는 모두 같은 RLMRealmConfiguration을 사용해야 합니다.

하나의 트랜잭션 내에서 동시에 여러 변경 사항을 일괄 처리하여 많은 양의 데이터를 기록할 때 Realm은 상당히 효율적입니다. 트랜잭션은 또한 메인 스레드를 차단하지 않도록 Grand Central Dispatch를 사용하여 백그라운드로 수행할 수 있습니다. RLMRealm 객체는 스레드로부터 안전하지 않은 스레드를 통해 공유할 수 없습니다. 그래서 읽거나 쓰고자 하는 각 thread/dispatch queue에 Realm 인스턴스를 받아야 합니다. 아래는 백그라운드 큐에 백만 개의 개체를 삽입하는 예입니다:

dispatch_async(queue, ^{
  @autoreleasepool {
    // Get realm and table instances for this thread
    RLMRealm *realm = [RLMRealm defaultRealm];

    // Break up the writing blocks into smaller portions
    // by starting a new transaction
    for (NSInteger idx1 = 0; idx1 < 1000; idx1++) {
      [realm beginWriteTransaction];

      // Add row via dictionary. Property order is ignored.
      for (NSInteger idx2 = 0; idx2 < 1000; idx2++) {
        [Person createInRealm:realm
                    withValue:@{@"name"      : randomString,
                                @"birthdate" : randomDate}];
      }

      // Commit the write transaction
      // to make this data available to other threads
      [realm commitWriteTransaction];
    }
  }
});

JSON

Realm은 JSON을 직접 지원하지는 않지만, [NSJSONSerialization JSONObjectWithData:options:error:] 를 사용해서 JSON으로부터 RLMObject를 추가할 수 있습니다. 결과 객체는 KVC에 적합하며, 표준 API를 사용해서 RLMObject의 추가 및 업데이트가 가능합니다.

// A Realm Object that represents a city
@interface City : RLMObject
@property NSString *name;
@property NSInteger cityId;
// other properties left out ...
@end
@implementation City
@end // None needed

NSData *data = [@"{\"name\": \"San Francisco\", \"cityId\": 123}" dataUsingEncoding: NSUTF8StringEncoding];
RLMRealm *realm = [RLMRealm defaultRealm];

// Insert from NSData containing JSON
[realm transactionWithBlock:^{
  id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
  [City createOrUpdateInRealm:realm withValue:json];
}];

JSON에 중첩 객체나 배열이 있다면 1 대 1과 1대 다 관계로 자동적으로 매핑됩니다. 자세한 내용은 중첩 객체를 확인하세요.

이 방법으로 Realm에 JSON 데이터를 넣거나 업데이트한다면, JSON 속성 이름과 타입이 정확히 RLMObject 속성과 일치해야 합니다. 예를 들면

  • float 속성은 float 기반의 NSNumbers로 초기화해야 합니다.
  • NSDateNSData 속성은 문자열로부터 자동으로 추론되지 않으며, [RLMObject createOrUpdateInRealm:withValue:] 로 넘기기 전에 변환돼야 합니다.
  • JSON null (예. NSNull)이 항상 값이 존재해야 하는 속성에 적용되면 예외가 발생합니다.
  • 삽입 시에 항상 값이 존재해야 하는 속성이 없다면 예외가 발생합니다.
  • Realm은 `RLMObject에 정의되지 않은 JSON 내의 모든 속성을 무시합니다.

JSON 스키마가 Realm 객체와 정확히 일치하지 않는다면 JSON 변형을 위해 제 3자 모델 매핑 프레임워크를 사용하는 것을 권장합니다. Objective‑C는 잘 관리되는 모델 매핑 프레임워크를 갖고 있으며, 일부는 Realm-cocoa repository 목록에서 확인할 수 있습니다.

알림

addNotificationBlock 메서드를 호출하는 것으로 RLMRealm, RLMResults, RLMArray, RLMLinkingObjects가 업데이트될 때마다 통지를 받도록 등록할 수 있습니다.

또한 단일 RLMObject의 변화를 감지하기 위해 Key-Value Observation을 사용할 수도 있습니다.

알림이 즉각적으로 전달되지 못하는 경우라면, 다중의 쓰기 트랜잭션을 단일 알림으로 묶을 수도 있습니다.

반환되는 알림 토큰에 레퍼런스를 참조하는 동안 만큼 알림은 구독됩니다. 토큰이 메모리 해제되었을 때 자동적으로 알림이 구독 취소되므로 업데이트를 등록한 클래스 내의 토큰에 참조를 걸어두세요.

Realm 알림

Realm 객체는 쓰기 트랜잭션이 커밋될 때마다 여러 스레드의 다른 객체에 알림을 보냅니다.

// Observe Realm Notifications
token = [realm addNotificationBlock:^(NSString *notification, RLMRealm * realm) {
    [myViewController updateUI];
}];

// later
[token stop];

컬렉션 알림

컬렉션 알림은 Realm 알림과 조금 다릅니다. 마지막 알림 이후로 삽입, 삭제, 수정된 객체의 인덱스를 포함해서 상세한 수준에서 어떤 변화가 생겼는지 묘사하는 정보를 포함하고 있습니다. 이를 통해 알림을 받을 때마다 모든 것을 다시 로딩하지 않고도, 애니메이션과 UI 내의 콘텐츠의 가시적 업데이트를 분리해서 컨트롤할 수 있습니다.

컬렉션 알림은 비동기적으로 전달됩니다. 처음에는 최초 결과를 전달하고, 컬렉션 내의 객체 변화를 일으키는 각 쓰기 트랜잭션 이후에 전달됩니다.

이런 변화는 알림 블록으로부터 전달된 RLMCollectionChange 매개변수를 통해 접근할 수 있습니다.

- (void)viewDidLoad {
  [super viewDidLoad];

  // Observe RLMResults Notifications
  __weak typeof(self) weakSelf = self;
  self.notificationToken = [[Person objectsWhere:@"age > 5"] addNotificationBlock:^(RLMResults<Person *> *results, RLMCollectionChange *change, NSError *error) {
    if (error) {
      NSLog(@"Failed to open Realm on background worker: %@", error);
      return;
    }

    UITableView *tableView = weakSelf.tableView;
    // Initial run of the query will pass nil for the change information
    if (!changes) {
      [tableView reloadData];
      return;
    }

    // Query results have changed, so apply them to the UITableView
    [tableView beginUpdates];
    [tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0]
                     withRowAnimation:UITableViewRowAnimationAutomatic];
    [tableView insertRowsAtIndexPaths:[changes insertionsInSection:0]
                     withRowAnimation:UITableViewRowAnimationAutomatic];
    [tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0]
                     withRowAnimation:UITableViewRowAnimationAutomatic];
    [tableView endUpdates];
  }];
}
- (void)dealloc {
  [self.notificationToken stop];
}

인터페이스 기반 쓰기

Realm의 알림은 항상 비동기적으로 전달되므로 앱이 느려지거나 메인 UI 스레드를 차단하지 않습니다. 하지만 변경 사항이 메인 스레드에서 동기적으로 수행되고 UI에 즉각 반영돼야 하는 상황이 있을 수 있습니다. 이러한 트랜잭션을 인터페이스 기반 쓰기라고 부릅니다.

예를 들어 사용자가 테이블뷰에 항목을 추가한 경우라면 사용자가 작업을 시작하자마자 UI가 이 작업을 이상적인 애니메이션으로 보여주는 동시에 해당 프로세스를 시작해야 합니다.

이 때 이 삽입에 대한 Realm 변경 알림이 조금 늦게 전달된다면 테이블 뷰가 사용하는 컬렉션에 객체가 추가된 것을 알리고 다시 새 항목이 UI에 추가될 수도 있습니다. 이런 중복된 삽입은 UI와 데이터 사이의 상태의 일관성을 해치고 💥NSInternalInconsistencyException💥 과 같은 예외와 함께 앱을 크래시하게 됩니다.

인터페이스 기반 쓰기를 수행하는 경우에는 변경 사항에 두 번 반응하지 않도록 알림 블록의 알림 토큰을 -[RLMRealm commitWriteTransactionWithoutNotifying:error:]Realm.commitWrite(withoutNotifying:)에 전달합니다.

이 기능은 특히 동기화 Realm과 함께 세분화된 컬렉션 알림을 사용할 때 좋습니다. 이전에는 인터페이스 기반 쓰기를 구현하기 위한 우회책으로 앱의 전체 변경 상태를 제어해야 했지만, 동기화 Realm을 사용하면 동기화될 때마다 변경이 적용되고 앱의 어떤 생명주기 상태에서도 변경할 수 있습니다.

// Observe RLMResults Notifications
__weak typeof(self) weakSelf = self;
self.notificationToken = [self.collection addNotificationBlock:^(RLMResults<Item *> *results, RLMCollectionChange *changes, NSError *error) {
  if (error) {
    NSLog(@"Failed to open Realm on background worker: %@", error);
    return;
  }

  UITableView *tableView = weakSelf.tableView;
  // Initial run of the query will pass nil for the change information
  if (!changes) {
    [tableView reloadData];
    return;
  }

  // Query results have changed, so apply them to the UITableView
  [tableView beginUpdates];
  [tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0]
                   withRowAnimation:UITableViewRowAnimationAutomatic];
  [tableView insertRowsAtIndexPaths:[changes insertionsInSection:0]
                   withRowAnimation:UITableViewRowAnimationAutomatic];
  [tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0]
                   withRowAnimation:UITableViewRowAnimationAutomatic];
  [tableView endUpdates];
}];

- (void)insertItem {
  // Perform an interface-driven write on the main thread:
  [self.collection.realm beginWriteTransaction];
  [self.collection insertObject:[Item new] atIndex:0];
  // And mirror it instantly in the UI
  [tableView insertRowsAtIndexPaths:[NSIndexPath indexPathForRow:0 inSection:0]
                   withRowAnimation:UITableViewRowAnimationAutomatic];
  // Making sure the change notification doesn't apply the change a second time
  [self.collection.realm commitWriteTransactionWithoutNotifying:@[token]];
}

알림 API

보다 자세한 내용은 아래 API 문서에서 확인하세요.

Key-Value Observation

Realm 객체는 대부분의 속성에 대한 Key-Value Observation이 가능합니다. RLMObject 서브 클래스의 대부분의 관리 대상 (무시가 아닌) 속성들은 RLMObjectRLMArray에 무효화된 속성들과 함께 KVO가 가능합니다. (RLMLinkingObjects 속성은 KVO를 사용해서 구독할 수 없습니다.)

RLMObject 서브 클래스인 관리되지 않는 객체의 Observing 속성은 다른 NSObject 서브 클래스와 같이 작동하지만, 등록된 옵저버를 갖는 동안 Realm에 ([realm addObject:obj] 또는 비슷한 메서드와 함께) 객체를 추가할 수 없다는 것을 명심하세요.

(이전에 Realm에 추가된) 관리되는 객체에 대한 Observing properties는 조금 다르게 작동합니다. 관리되는 객체에서 해당 속성의 변화 타이밍이 3가지가 있습니다. 속성에 값을 직접 대입할 때 [realm refresh] 메소드를 호출했을 때, 또는 다른 스레드가 트랜잭션을 커밋하여 자동으로 Realm이 업데이트 되었을 때, 그리고 다른 스레드에서 변경이 있었지만 그 전에 [realm beginWriteTransaction] 를 호출하여 트랜잭션을 시작했기 때문에 변경이 통지되지 않은 때 등이 있습니다.

직접 할당하는 경우 외에 2 개의 경우는 모든 다른 스레드에서 변경된 내용은 한 번에 적용이 됩니다. 따라서 KVO의 통지는 1회에 정리합니다. 도중에 변경 상태는 파기되므로 1에서 10까지 하나씩 수를 증가시키는 속성이 있는 경우에는 1에서 10으로 변경되면서 하나의 통보만 받게 됩니다. 트랜잭션 외부에서 속성이 변경 될 수 있으므로 -observeValueForKeyPath : ofObject : change : context : 메소드에서 관리되는 Realm 객체를 변경하는 것을 권장하지 않습니다.

NSMutableArray 속성과 달리 RLMArray 속성 변경을 observing 하려면 -mutableArrayValueForKey : 를 사용할 필요는 없습니다 (다만 호환성을 위해 이를 사용해도 동일하게 작동합니다). 직접 RLMArray를 변경하는 메소드를 호출하면 (모니터링하고 있으면) 업데이트가 통지됩니다.

ReactiveCocoa (Objective‑C)ReactKit (Swift)에 Realm 사용을 위한 간단한 샘플 앱이 있으므로 참고하세요.

마이그레이션

다른 어떤 데이터베이스로 작업할 때와 마찬가지로 데이터 모델은 시간이 지남에 따라 변경됩니다. 표준 Objective‑C 클래스로 Realm의 데이터 모델을 정의하고 다른 Objective‑C 클래스를 수정하는 것처럼 모델을 수정하기 간편합니다. 예를 들어, 아래와 같은 Person 모델이 있다고 가정합니다:

@interface Person : RLMObject
@property NSString *firstName;
@property NSString *lastName;
@property int age;
@end

성과 이름을 따로 나누기 보다 ‘fullName’ 속성을 추가하길 바랍니다. 이러한 작업을 위해 다음과 같이 간단하게 객체 인터페이스를 수정합니다:

@interface Person : RLMObject
@property NSString *fullName;
@property int age;
@end

이 시점에 어떠한 데이터라도 저장했다면 코드에 정의된 모델과 디스크에 기록된 모델 사이에 불일치가 발생합니다. 이러한 상황이 발생할 때 기존 파일을 열려고 하면 마이그레이션이 진행되지 않고 예외 처리됩니다.

주의해야하는 것은 기본 속성 값은 마이그레이션 중의 새 객체나 기존 객체의 새 속성에 적용이 되지 않는다는 점입니다. 이 문제는 #1793를 참고하세요.

마이그레이션 실행

마이그레이션 정의

마이그레이션을 정의하고 +[RLMRealm setSchemaVersion:forRealmAtPath:withMigrationBlock:] 설정으로 관련된 버전을 정의합니다. 마이그레이션 블록은 이전 스키마에서 새로운 스카마로 데이터 모델을 변경하는 모든 로직을 제공합니다. 이러한 설정으로 RLMRealm을 작성할 때, 마이그레이션 블록은 마이그레이션이 필요한 경우 주어진 스키마 버전에 RLMRealm 업데이트가 적용됩니다.

예를 들어, 상단의 Person 서브 클래스를 마이그레이션한다고 가정합니다. 최소한의 필요한 마이그레이션 블록은 아래와 같습니다.

// Inside your [AppDelegate didFinishLaunchingWithOptions:]

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
config.schemaVersion = 1;
// Set the block which will be called automatically when opening a Realm with a
// schema version lower than the one set above
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  // We haven’t migrated anything yet, so oldSchemaVersion == 0
  if (oldSchemaVersion < 1) {
    // Nothing to do!
    // Realm will automatically detect new properties and removed properties
    // And will update the schema on disk automatically
  }
};
// Tell Realm to use this new configuration object for the default Realm
[RLMRealmConfiguration setDefaultConfiguration:config];
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
[RLMRealm defaultRealm];

최소한 해야할 일은 Realm이 (자동적으로) 업그레이드한 스카마를 가르키는 빈 블록과 버전을 수정하는 일입니다.

값 업데이트

최소 허용 마이그레이션이긴 하지만, 아마도 이 블록을 사용해 무언가 의미있는 새로운 속성(이 경우 fullName)을 만들고 싶을 것입니다. 마이그레이션 블록 내에서 우리는 특정 유형의 각 RLMObject를 열거하는 [RLMMigration enumerateObjects:block:]의 호출을 할 수 있고, 필요한 마이그레이션 로직을 적용할 수 있습니다. 각 열거형에 대한 기존 RLMObject 인스턴스는 oldObject 변수에 접근할 수 있고, 업데이트된 인스턴스는 newObject를 통해 접근할 수 있습니다.

// Inside your [AppDelegate didFinishLaunchingWithOptions:]

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 1;
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  // We haven’t migrated anything yet, so oldSchemaVersion == 0
  if (oldSchemaVersion < 1) {
    // The enumerateObjects:block: method iterates
    // over every 'Person' object stored in the Realm file
    [migration enumerateObjects:Person.className
                          block:^(RLMObject *oldObject, RLMObject *newObject) {

      // combine name fields into a single field
      newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@",
                                         oldObject[@"firstName"],
                                         oldObject[@"lastName"]];
    }];
  }
};
[RLMRealmConfiguration setDefaultConfiguration:config];

마이그레이션이 성공적으로 완료되면 앱을 통해 Realm 및 모든 개체를 평소와 같이 접근할 수 있습니다.

속성 이름 변경

마이그레이션으로 클래스의 속성 이름을 변경하는 것은 값을 복사하는 것보다 효율적이며 중복을 생성하는 것보다 관계를 잘 보존합니다.

마이그레이션 중에 속성의 이름을 변경하려면 새 모델이 새로운 이름의 속성만을 갖고 예전 이름을 갖지 않도록 주의해야 합니다.

새 속성이 다른 nullability나 인덱스 세팅을 가진다면 이름을 변경하는 과정에서 적용됩니다.

PersonyearsSinceBirth 속성을 age로 변경하는 예제를 살펴 보세요.

// Inside your [AppDelegate didFinishLaunchingWithOptions:]

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 1;
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  // We haven’t migrated anything yet, so oldSchemaVersion == 0
  if (oldSchemaVersion < 1) {
    // The renaming operation should be done outside of calls to `enumerateObjects:`.
    [migration renamePropertyForClass:Person.className oldName:@"yearsSinceBirth" newName:@"age"];
  }
};
[RLMRealmConfiguration setDefaultConfiguration:config];

버전 추가

두 버전의 Person 클래스가 있다고 가정합니다.

// v0
// @interface Person : RLMObject
// @property NSString *firstName;
// @property NSString *lastName;
// @property int age;
// @end

// v1
// @interface Person : RLMObject
// @property NSString *fullName; // new property
// @property int age;
// @end

// v2
@interface Person : RLMObject
@property NSString *fullName;
@property NSString *email;   // new property
@property int age;
@end

마이그레이션 블록의 로직은 다음과 같습니다:

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 2;
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  // The enumerateObjects:block: method iterates
  // over every 'Person' object stored in the Realm file
  [migration enumerateObjects:Person.className
                        block:^(RLMObject *oldObject, RLMObject *newObject) {
    // Add the 'fullName' property only to Realms with a schema version of 0
    if (oldSchemaVersion < 1) {
      newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@",
                                oldObject[@"firstName"],
                                oldObject[@"lastName"]];
    }

    // Add the 'email' property to Realms with a schema version of 0 or 1
    if (oldSchemaVersion < 2) {
      newObject[@"email"] = @"";
    }
  }];
};
[RLMRealmConfiguration setDefaultConfiguration:config];

// now that we have updated the schema version and provided a migration block,
// opening an outdated Realm will automatically perform the migration and
// opening the Realm will succeed
[RLMRealm defaultRealm];

데이터 스키마의 마이그레이션 구현에 대한 더욱 자세한 내용은 마이그레이션 예제 앱을 확인하세요.

선형 마이그레이션

어떤 앱의 두 사용자 JP와 Tim이 있다고 가정해 봅니다. JP는 매우 자주 수정을 하는 반면 Tim은 몇몇 버전은 건너뛰었습니다. JP는 앱의 모든 새 버전의 앱을 보고 매번 스키마를 업그레이드했을 것입니다. 그는 앱을 다운로드 할때마다, v0에서 v1으로 v1에서 v2로 수정했을 겁니다. 반면에 Tim이 다운로드를 하였을 경우에는 갑자기 v0에서 v2로 변경하여야 했을 겁니다. 따라서 시작하는 버전에 관계 없도록 비종속적으로 if (oldSchemaVersion < X)와 같이 모든 업그레이드가 보이도록 마이그레이션 블록을 구조화 해야 합니다.

또 다른 시나리오는 버전을 건너뛰는 사용자에게서 발생 할 수 있습니다. 만약 Version 2에서 “email” 속성을 삭제하고, Version 3에서 다시 재도입하였고, 사용자는 Version 1에서 Version 3로 건너뛰었을 때, 그 코드 안의 해당 속성에 대한 스키마와 디스크상의 스키마에 불일치가 없어서 Realm은 “email” 속성의 삭제를 자동적으로 감지하지 못합니다. Tim의 Person 객체는 v3의 address 속성은 v1의 address 속성을 따라 갑니다. 이것은 v1 및 v3 사이의 속성에 대해서 내부 저장소의 표현을 변경(말하자면, ISO 주소 표현을 사용자 정의로 변경하는 것과 같은)하지 않는 한 문제가 되지 않을 수도 있습니다. 이와 같은 것을 피하기 위해서 모든 버전에 대해서 또는 “email” 속성에 대해 if (oldSchemaVersion < 3)와 같이 사용하여 모든 Realm이 version 3로 업그레이드 되어 정상적인 데이터셋을 가지도록 보장할 것을 권고합니다.

암호화

최소 허용 마이그레이션 하는 동안, Realm의 암호화 API는 iOS 와 WatchKit, iO X 용으로 사용할 수 있지만, watchOS는 Realm 암호화 메커니즘을 사용한 <mach/mach.h><mach/exc.h> API은 __WATCHOS_PROHIBITED으로 표시되어 있기 때문에 사용할 수 없습니다.

우리는 이 문제에 대하여 레이더를 제기했습니다: rdar://22063654.

미국으로부터의 수출 제한이나 금수 조치가 있는 국가에 거주하는 경우 Realm 사용에 대한 제한이 있을 수 있으므로, 저희 라이센스의 수출 규정 준수 조항을 참고하십시오.

Realm은 생성 시에 64-바이트 암호화 키를 제공할 경우 AES-256+SHA2 방식으로 디스크 내 데이터베이스 파일의 암호화를 지원합니다.

// Generate a random encryption key
NSMutableData *key = [NSMutableData dataWithLength:64];
(void)SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes);

// Open the encrypted Realm file
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.encryptionKey = key;
NSError *error = nil;
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
if (!realm) {
    // If the encryption key is wrong, `error` will say that it's an invalid database
    NSLog(@"Error opening realm: %@", error);
}

// Use the Realm as normal
RLMResults<Dog *> *dogs = [Dog objectsInRealm:realm where:@"name contains 'Fido'"];

디스크 내 저장된 모든 데이터를 필요시에 AES-256 방식으로 암호화하고 복호화합니다. 그리고 SHA-2 HMAC 방식으로 확인합니다. Realm 인스턴스를 생성할 때 동일한 암호화 키를 사용해야 합니다.

암호화 키를 생성하고 키체인에 저장하고 Realm에서 사용하기까지 면밀히 살펴보기 위해 우리의 암호화 샘플 앱을 보세요.

암호화된 Realm을 사용할 때에는 다소 속도 하락(대체로 10% 이하)이 존재합니다.

외부 크래쉬 리포터(Crashlytics, PLCrashReporter 등)는 암호화된 Realm을 열기 전에 등록되어야 하고 그렇지 않으면 잘못된 경고 알림을 받게 됩니다.

테스팅과 디버깅

기본 Realm 설정을 변경

Realm을 사용하고 테스트하기에 가장 적절한 방법은 기본 Realm을 사용하는 방법입니다.

실제 응용 프로그램 데이터를 덮어 버리는 것을 막기 위해, 혹은 각 테스트의 상태가 다른 테스트에 영향을주는 것을 방지하기 위해 각각의 테스트에서 새로운 Realm 파일을 사용하도록 기본 Realm을 설정합니다.

// A base class which each of your Realm-using tests should inherit from rather
// than directly from XCTestCase
@interface TestCaseBase : XCTestCase
@end

@implementation TestCaseBase
- (void)setUp {
  [super setUp];

  // Use an in-memory Realm identified by the name of the current test.
  // This ensures that each test can't accidentally access or modify the data
  // from other tests or the application itself, and because they're in-memory,
  // there's nothing that needs to be cleaned up.
  RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
  config.inMemoryIdentifier = self.name;
  [RLMRealmConfiguration setDefaultConfiguration:config];
}
@end

RLMRealm 인스턴스 삽입

Realm과 관련된 코드를 테스트하는 또 다른 방법은 테스트하려는 모든 메소드가 RLMRealm 객체를 받고 테스트의 경우 다른 Realm 객체를 넘겨주는 방법입니다. 예를 들어, JSON API로부터 유저 정보를 GET 요청하는 메소드가 있다면 로컬 유저 정보가 즉각적으로 생성되는지 테스트할 수 있습니다.

// Application Code

@implementation ClassBeingTested
+ (void)updateUserFromServer {
  NSURL *url = [NSURL URLWithString:@"http://myapi.example.com/user"];
  [[[NSURLSession sharedSession] dataTaskWithURL:url
                               completionHandler:^(NSData *data,
                                                   NSURLResponse *response,
                                                   NSError *error) {
    [self createOrUpdateUserInRealm:[RLMRealm defaultRealm] withData:data];
  }] resume];
}

+ (void)createOrUpdateUserInRealm:(RLMRealm *)realm withData:(NSData *)data {
  id object = [NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingOptions)nil error:nil];
  [realm transactionWithBlock:^{
    [User createOrUpdateInRealm:realm withValue:object];
  }];
}
@end

// Test Code

@implementation UnitTests
- (void)testThatUserIsUpdatedFromServer {
  RLMRealm *testRealm = [RLMRealm realmWithURL:kTestRealmURL];
  NSData *jsonData = [@"{\"email\": \"[email protected]\"}"
                      dataUsingEncoding:NSUTF8StringEncoding];
  [ClassBeingTested createOrUpdateUserInRealm:testRealm withData:jsonData];
  User *expectedUser = [User new];
  expectedUser.email = @"[email protected]";
  XCTAssertEqualObjects([User allObjectsInRealm:testRealm][0],
                        expectedUser,
                        @"User was not properly updated from server.");
}
@end

디버깅

LLDB 지원과 실시간으로 Realm Browser을 통해 앱 내부 데이터를 볼 수 있어서 손쉽게 Realm 앱을 디버깅할 수 있습니다.

우리의 Xcode 플러그인에 Xcode UI에서 RLMObject, RLMResults, RLMArray 객체 디버깅을 지원하는 LLDB 스크립트가 포함되어 있어 속성 값이 nil 혹은 0으로만 표시되지 않습니다.

Xcode 스크린샷

알림: 해당 스크립트는 현재 Objective‑C만 지원합니다. Swift 지원을 준비 중입니다.

테스트 타깃에서 Realm.framework와 테스트 코드의 링킹 회피하기

동적 프레임워크로 Realm을 사용하는 경우, 단위 테스트의 대상이 Realm 프레임워크를 찾을 수 있도록 해 둘 필요가 있습니다. 이를 위해 단위 테스트 “:Framework Search Paths”에 Realm.framework의 부모 폴더 위치를 추가합니다.

"Object type 'YourObject' is not managed by the Realm"과 같은 예외 처리 문구와 함께 테스트가 실패했다면, Realm.framework를 테스트 타깃에 링크했기 때문입니다. Realm을 테스트 타깃에서 제외하세요.

또한 모델 클래스 파일은 응용 프로그램의 대상 또는 프레임 워크의 대상 어느 한쪽에만 연결되어 있어야 합니다.

그렇지 않으면 해당 클래스는 테스트 도중 중복 정의되고 디버기 하기 어려운 이슈가 생길 수 있습니다. (자세한 정보는 https://github.com/realm/realm-cocoa/issues/1350 에서 확인하세요.)

public 한정자나, Swift 2.0의 @testable을 사용해서, 테스트하려는 모든 코드를 유닛테스트 타깃에 노출해야 합니다. 자세한 내용은 Stack Overflow를 참조하세요.

현재 제한 사항

Realm의 가장 일반적인 제한 사항을 정리했습니다.

알려진 문제점을 알아보기 위해 GitHub 이슈에 방문해주세요.

일반적인 제한

Realm은 사용성과 속도의 균형을 목표로 하고 있습니다. 이러한 목표를 달성하기 위해, Realm에서 정보를 저장하는 다양한 측면에서 현실적인 제한이 존재합니다. 예를 들어:

  1. 클래스 이름은 최대 UTF8 57자로 제한됩니다.
  2. 속성 이름은 최대 UTF8 63자로 제한됩니다.
  3. NSData, NSString 속성은 16MB 크기로 제한됩니다. 더 큰 데이터를 저장하기 위해서는 16MB 단위의 작은 파일로 나누거나 파일 시스템에 직접 저장하고 경로를 Realm에 저장하세요. 단일 속성이 16MB를 초과하는 데이터를 저장하려고 시도하면 예외 처리됩니다.
  4. 단일 Realm 파일 크기는 iOS가 대응하는 앱의 메모리 영역 크기를 넘어설 수 없습니다. 이는 디바이스별로 다르고 해당 시점에 얼마나 메모리 공간이 단편화되었는지에 따라 다릅니다 (해당 이슈에 대한 레이더가 있습니다: rdar://17119975). 더 많은 데이터를 저장할 필요성이 있다면 다수의 Realm 파일과 연동할 수 있습니다.
  5. 문자열 소팅과 대소문자 구분 쿼리는 ‘Latin Basic’, ‘Latin Supplement’, ‘Latin Extended A’, ‘Latin Extended B’ (UTF-8 range 0-591) 캐릭터 셋에만 지원됩니다.

스레드 제한

Realm 파일은 여러 스레드에서 동시에 접근 가능하지만, 스레드 간의 Realms, Realm 객체, 쿼리, 결과값을 직접 전달하는 것은 불가능합니다. 만약 스레드 간 Realm 객체 전달이 필요하다면 RLMThreadSafeReference API를 사용할 수 있습니다. 스레드 항목에서 Realm의 스레드 정책에 대해 알아보세요.

Realm 객체 setter 및 getter는 재정의할 수 없습니다.

Realm이 내부 데이터베이스로 속성을 다루는 위해 setter와 getter를 재정의하기 때문에 객체를 대상으로 재정의할 수 없습니다. 해결책은 재정의가 가능한 Realm-ignore 속성을 새로 할당하고 호출하는 방법입니다.

파일 크기 및 중간 버전 추적

Realm 쓰기 트랜잭션의 생명 주기는RLMRealm 인스턴스의 메모리 수명에 연결됩니다. Realm의 자동 새로 고침을 사용하고 모든 Realm API의 사용을 백그라운드 스레드의 명시적 오토릴리즈 풀로 감싸서 예전 Realm 트랜잭션이 고정되는 것을 피하세요.

SQLite 보다 더 적은 용량으로 데이터를 디스크에 저장하길 기대하실 겁니다. Realm 파일의 용량이 생각보다 큰 경우 RLMRealm은 오래된 기록 데이터를 조회할 것입니다.

데이터의 일관성을 유지하기 위해 Realm은 최신 데이터에 액세스한 경우에만 기록을 업데이트합니다. 다른 스레드가 많은 데이터를 긴 시간을 들여 쓰는 동안 데이터를 조회하려고 하면 기록이 업데이트되지 않고 이전 데이터를 읽을 수 있습니다. 결과적으로, 기록 중간 데이터가 증가할 수 있습니다.

이 추가 공간은 결국 재사용되거나 압축됩니다. (예를 들면 shouldCompactOnLaunch를 설정하거나 writeCopyToPath:error: 호출을 사용하여 파일을 복사합니다. 그 때 자동으로 파일 크기가 최적화됩니다.)

이 문제를 방지하려면 invalidate 메소드를 호출해서 Realm에 지금까지 얻은 데이터는 더 이상 필요없음을 알려주세요. 이 경우 Realm은 중간 데이터의 기록을 해제합니다. 그리고 다음 액세스할 때 최신 데이터를 사용하도록 업데이트합니다.

또한 GCD를 사용하여 Realm에 액세스한 경우에도 이 문제가 발생할 수 있습니다. 블록의 실행이 종료된 후에도 오토 릴리즈 풀 개체가 해제되지 않고 RLMRealm이 해제될 때까지 오래된 기록 데이터가 남게됩니다.

이 문제를 피하기 위해 dispatch queue에서 Realm에 액세스할 때 명시적으로 오토 릴리즈 풀을 이용하십시오.

자동 증가 속성 미지원

Realm은 다른 데이터베이스가 기본키를 생성할 때 주로 사용하는 스레드나 프로세스에 안전한 자동 증가 속성을 지원하지 않습니다. 다만 고유한 자동 증가 값이 필요한 경우, 대부분 순차적인 정수 ID가 필요하지는 않습니다.

이 경우 고유한 문자열 기본키로도 충분할 수 있습니다. 대부분의 패턴에서 기본 속성값을 [[NSUUID UUID] UUIDString]로 지정해서 고유한 문자열 ID를 생성합니다.

자동 증가 속성이 필요한 다른 경우는 삽입 순서를 보존하기 위해서입니다. RLMArray에 객체를 넣거나 기본 값이 [NSDate date]createdAt 속성을 사용하는 것으로 해당 목적을 달성할 수 있습니다.

Realm API로 Swift 속성 초기화

만약 Swift 애플리케이션을 만드는 경우라면 Swift 앱의 클래스와 구조체는 다음 예제와 같이 Realm API를 이용해서 초기화된 값으로 속성을 정의해야 할 수 있습니다.

class SomeSwiftType {
  let persons = RLMPerson.allObjects(in: RLMRealm.default())
  // ...
}

이러한 속성을 사용해서 타입을 정의하는 경우에 이런 초기화 코드가 Realm의 설정을 마치기 전에 불린다면 문제가 될 수 있습니다.

예를 들어 applicationDidFinishLaunching()에 기본 Realm 설정을 위한 마이그레이션 블록을 설정했지만, applicationDidFinishLaunching()가 실행되기 전에 SomeSwiftType의 인스턴스를 만들었고, Realm이 마이그레이션을 요청하는 경우 Realm이 제대로 구성되기 전에 접근하게 됩니다.

이러한 문제를 피하기 위해서는 다음과 같은 선택 사항이 있습니다.

  1. 앱이 사용하는 Realm 설정을 마칠 때까지는 Realm API를 사용해서 속성을 초기화하는 모든 타입의 인스턴스 작성을 하지 않습니다.
  2. Swift의 lazy 키워드를 사용해서 속성을 정의합니다. 이를 통해 앱에서 Realm 설정을 마칠 때까지 lazy 속성에 접근하지 않는 한 이런 타입을 앱의 어느 생명 주기 상에서나 초기화할 수 있습니다.
  3. 명시적인 사용자 정의 구성을 갖는 Realm API만을 속성 초기화에 사용합니다. 이를 통해 설정값을 사용해서 Realm을 열기 전에 적절하게 이 값을 구성했는지 확인할 수 있습니다.

암호화된 Realm에는 여러 프로세스가 동시에 접근할 수 없습니다.

여기에는 iOS 익스텐션도 포함됩니다. 이를 우회하기 위해서는 프로세스간에 공유할 수 있도록 암호화되지 않은 Realm을 사용해야 합니다. Security 및 CommonCrypto 시스템 프레임워크를 사용하여 Realm 객체의 NSData 속성에 저장된 데이터를 암호화하거나 해독할 수 있습니다.

이런 한계에 대해 Realm Cocoa 이슈 트래커(#1693)와 Realm Core 이슈 트래커(#1845)에서 파악하고 있습니다.

-[NSPredicate evaluateWithObject:]가 Realm 컬렉션을 비 컬렉션 객체라고 거부할 때

NSPredicate 내부의 지나치게 엄격한 검사 구현 때문에 몇몇 NSPredicate API는 Realm 컬렉션 타입과 호환되지 않습니다. 예를 들어 -[NSPredicate evaluateWithObject:]는 서브 쿼리의 조건자가 Realm 컬렉션을 순회할 때 예외를 던집니다.

Apple은 이 이슈에 대해 인지하고 있습니다. (rdar://31252694).

애플리케이션에서 이 문제를 해결해야 하는 경우 PR #4770 패치를 하고, 조건자를 수행하기 전에 RLMWorkaroundRadar31252694()를 한 번만 실행하세요.

레시피

Realm을 사용해서 특정 기능을 달성하는 예제들을 레시피로 정리했습니다. 레시피는 주기적으로 추가되므로 많은 방문 바랍니다. 아직 다루지 않은 특정 주제에 관심이 있다면, GitHub에 이슈를 열어 주세요.

동기화

Realm Mobile Platform은 Mobile Database의 기능을 확장해서 디바이스 간 데이터의 자동 동기화를 가능하게 합니다. 추가적인 기능인 Realm의 동기화를 지원하기 위해 여러 타입과 클래스가 제공합니다. Realm 모바일 플랫폼 API에 대한 추가 기능 확장인 이러한 동기화 관련 API를 소개합니다.

사용자(User)

동기 Realm과 관련된 사용자 는 Realm 모바일 플랫폼의 기본 구성 요소입니다. 사용자는 RLMSyncUser 형식으로 표현되며, 사용자 이름/비밀번호, 혹은 여러 서드 파티 인증 방법을 통해 공유 Realm에서 인증될 수 있습니다.

사용자를 생성하고 로그인하기 위해서는 두 가지 값이 필요합니다. * 접속하려는 Realm 오브젝트 서버의 URL * 사용자를 식별하는 인증 정보에 적합한 것으로 사용자를 증명하는 자격 증명 (예를 들어 사용자 이름+비밀번호, 엑세스 키 등)

서버 URL

_인증 서버 URL_은 NSURL로, Realm 서버 위치를 지정합니다.

// end declarations
NSURL *serverURL = [NSURL URLWithString:@"http://my.realmServer.com:9080"];

인증 문서에서 서버 URL과 관련된 더 자세한 정보를 볼 수 있습니다.

인증

인증은 사용자의 신원을 확인하고 로그인하는 데 사용됩니다. 지원하는 인증 제공자에 대한 정보는 Realm Object Server 인증 문서에서 확인하세요. 아래 예제는 Realm이 지원하는 다양한 인증 제공자를 위한 credential 설정에 대한 예제입니다.

특정 사용자의 credential 정보RLMSyncCredentials 값으로 표현됩니다. 이 값은 여러 방법으로 생성할 수 있습니다.

  • 적합한 사용자 이름/비밀번호 조합
  • 지원되는 서드 파티 인증 서비스에서 얻은 토큰
  • 토큰 및 사용자 지정 인증 공급자(자세한 내용은 사용자 지정 인증를 참조하세요)

사용자 이름 및 비밀번호 인증은 애플리케이션의 사용자를 완벽하게 관리할 수 있도록 Realm 오브젝트 서버에서 전적으로 관리합니다. 다른 인증 방법의 경우 애플리케이션에서 서드 파티 서비스에 로그인하고 인증 토큰을 받아야 합니다.

다양한 공급자에서 자격 증명을 설명하는 몇 가지 예를 소개합니다.

사용자 이름/비밀번호
RLMSyncCredentials *usernameCredentials = [RLMSyncCredentials credentialsWithUsername:@"username"
                                                                             password:@"password"
                                                                             register:NO];

팩토리 메서드는 새로운 사용자를 등록해야 하는지 또는 기존 사용자로 로그인해야 하는지를 나타내는 register 불린 인자를 취합니다. 애플리케이션이 새로운 사용자를 기존 사용자 이름으로 등록하려 하거나 존재하지 않는 사용자로 로그인하려고 하면 에러가 발생합니다.

Google
RLMSyncCredentials *googleCredentials = [RLMSyncCredentials credentialsWithGoogleToken:@"Google token"];
Facebook
RLMSyncCredentials *facebookCredentials = [RLMSyncCredentials credentialsWithFacebookToken:@"Facebook token"];
Apple CloudKit
RLMSyncCredentials *cloudKitCredentials = [RLMSyncCredentials credentialsWithCloudKitToken:@"CloudKit token"];
사용자 지정 인증

Realm 오브젝트 서버는 외부 인증 공급자를 사용할 수 있도록 지원합니다. 이를 통해 사용자는 레거시 데이터베이스나 API에 대해 사용자를 인증하거나, Realm 오브젝트 서버에서 즉시 지원하지 않는 공급자와 통합할 수 있습니다. 사용자 정의 인증 공급자를 작성하는 자세한 방법은 오브젝트 서버 매뉴얼의 해당 섹션을 참고하세요.

RLMSyncCredentials *credentials = [[RLMSyncCredentials alloc] initWithCustomToken:@"custom token" provider:@"myauth" userInfo:nil];

다음과 같이 추가 정보를 사용자 지정 자격 증명 이니셜라이저에 세 번째 매개 변수로 전달할 수 있습니다. 자세한 내용은 API 문서를 참고하세요.

사용자 인증

사용자를 생성하려면 +[RLMSyncUser logIn]을 호출합니다.

이 팩토리 메서드는 사용자를 초기화하고 비동기적으로 Realm 오브젝트 서버에 로그인한 다음, 성공하면 콜백 블록에서 추가 사용을 위해 사용자 객체를 전달합니다. 로그인 프로세스가 실패하면 에러 객체가 대신 콜백 안에서 전달됩니다.

[RLMSyncUser logInWithCredentials:usernameCredentials
                    authServerURL:serverURL
                     onCompletion:^(RLMSyncUser *user, NSError *error) {
  if (user) {
    // can now open a synchronized RLMRealm with this user
  } else if (error) {
    // handle error
  }
}];

Realm Mobile Platform은 한 애플리케이션이 여러 명의 동시 사용자를 지원할 수 있도록 합니다. 예를 들어 여러 독립 계정의 연결을 지원하는 email 클라이언트 앱이 있을 경우, 활성 상태의 모든 사용자를 유지한 상태로 개인당 한 개의 이메일을 사용하는 여러 명의 사용자가 +[RLMSyncUser all]을 통해 인증될 수 있습니다.

로그아웃

애플리케이션의 사용자가 로그아웃을 할 경우, -[RLMSyncUser logOut]을 호출합니다.

보류 중인 로컬 변경 사항은 Realm 오브젝트 서버가 완전히 동기화될 때까지 계속 업로드되며, 그 다음 로컬에서 동기화된 Realm 파일은 다음 앱 실행 시에 디바이스에서 지워집니다.

어드민 사용자

어드민 사용자는 Realm 오브젝트 서버 인스턴스의 모든 Realm에 대한 관리 레벨 접근 권한이 있는 사용자입니다. 사용자가 어드민 사용자인지 여부는 마지막으로 성공한 로그인 당시의 사용자 상태를 반영하는 RLMSyncUser.isAdmin 속성에 반영됩니다.

[RLMSyncUser logInWithCredentials:usernameCredentials
                    authServerURL:serverURL
                     onCompletion:^(RLMSyncUser *user, NSError *error) {
  if (user) {
    // can now open a synchronized RLMRealm with this user
    // true if the user is an administrator on the ROS instance
    NSLog(@"User is admin: %@", @(user.isAdmin));
  } else if (error) {
    // handle error
  }
}];

동기화 Realm 열기

동기화 Realm은 설치형 Realm을 생성하는 것과 동일하게 RLMRealmConfiguration`과 팩토리 메서드를 사용해서 생성합니다.

동기화 Realm은 RLMRealmConfigurationsyncConfiguration 속성을 RLMSyncConfiguration 값으로 설정해야 합니다. RLMRealmConfigurationRLMSyncUser와 Realm 오브젝트 서버 상의 동기화 Realm Realm 위치를 나타내는 URL을 사용해서 구성됩니다. 이 URL은 (~) 물결 캐릭터를 포함하는데 사용자의 고유한 식별자로 이어집니다.

예를 들어 다음 Realm URL은 각 사용자가 realms://acme.example.com/~/widgets 복사본을 가진 “Widgets” Realm을 나타냅니다. 한 사용자는 고유한 식별자인 5917268를 가지고 있고, 이 사용자의 “Widgets” Realm을 나타내는 Realm URL은 realms://acme.example.com/5917268/widgets와 같이 확장됩니다. 8581230라는 고유한 식별자를 가지고 있는 다른 사용자의 Realm URL은 realms://acme.example.com/8581230/widgets와 같이 확장됩니다. 이를 통해 개별 사용자별로 고유한 URL 생성을 고심하지 않고도 모든 사용자가 접근하는 Realm의 현재 사용자를 위한 복사본을 나타내는 URL을 가지고 애플리케이션 로직을 작업할 수 있습니다.

주의 사항: URL은 “.realm” 파일 확장자로 끝날 수 없습니다. URL은 이름의 주요 부분 (예: 위의 “widgets”)이 포함돼야 하며, 데이터를 저장할 폴더와 관련 파일은 Realm이 생성합니다.

동기화 Realm을 위한 설정값은 inMemoryIdentifier나 fileURL을 구성할 수 없습니다. 두 속성 중 하나를 설정하면 자동으로 syncConfiguration` 속성이 nil 값이 됩니다. 프레임워크는 동기화 Realm이 캐시되거나 디스크에 저장하는 방법을 관리합니다.

다음 예제에서 사용자 객체와 Realm URL을 지정해서 동기화 Realm을 여는 방법을 확인하세요.

RLMSyncUser *user;

// Create the configuration
NSURL *syncServerURL = [NSURL URLWithString: @"realm://localhost:9080/~/userRealm"];
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:user realmURL:syncServerURL];

// Open the remote Realm
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
// Any changes made to this Realm will be synced across all devices!

세션 동기화

Realm 오브젝트 서버에 대한 동기화 Realm의 연결은 RLMSyncSession 객체로 표현됩니다. 특정 사용자가 연 Realm을 나타내는 세션 객체는 +[RLMSyncUser allSessions]-[RLMSyncUser sessionForURL:] API를 사용해서 해당 사용자의 RLMSyncUser 객체로부터 얻을 수 있습니다.

기본 작업

기본 세션의 상태는 state 속성을 사용해서 검색할 수 있습니다. 이를 통해 세션이 활성 상태인지, 서버에 연결되지 않았는지, 혹은 오류 상태인지 확인할 수 있습니다.

세션이 유효하다면 configuration 속성은 RLMSyncConfiguration 값을 포함하며, 이 값을 같은 Realm의 다른 인스턴스, 예를 들어 다른 스레드에 있는 인스턴스 등을 여는데 사용할 수 있습니다.

프로그레스 상태 알림

세션 객체를 사용하면 프로그레스 알림 블록 을 등록해서 앱에서 세션이 Realm 오브젝트 서버와 업로드 혹은 다운로드하는 상태를 모니터링할 수 있습니다.

프로그레스 알림 블록은 최초에 등록된 스레드 runloop의 동기화 서브시스템에 의해 주기적으로 호출됩니다. 만약 runloop가 없는 경우, 새 runroop가 생성됩니다. (실제로 GCD의 백그라운드 큐에 이 블록을 등록할 수 있으며 정상적으로 작동합니다.) 세션 객체에 필요한 만큼 여러 블록을 등록할 수 있으며, 블록은 업로드나 다운로드 프로그레스를 보고하도록 설정할 수 있습니다.

블록이 호출될 때마다 이미 전송된 바이트의 수와 전송 가능한 총 바이트 수(이미 전송된 바이트 수에 전송 보류된 바이트 수를 더한 값)를 받습니다.

블록이 등록되면, 등록 메서드는 토큰 객체를 반환합니다. 토큰에서 해당 특정 블록에 대한 알림을 중단하려면 -stop 메서드를 사용합니다. 이미 블록이 등록 해제됐다면 이 메서드는 아무 역할도 하지 않습니다. 만약 알림 블록이 다시는 실행되지 않을 경우 등록 메서드가 nil 토큰을 반환한다는 점을 주의하세요. 예를 들어 세션이 치명적인 에러 상태에 있거나 보고할 다음 프로그레스가 없는 경우 등입니다.

블록에는 두 가지 유형이 있습니다. 먼저 무기한으로 보고 하도록 구성할 수 있습니다. 이런 경우 명시적으로 사용자에 의해 중단되기 전까지 항상 전송 가능한 바이트 수를 최신으로 보고합니다. 이 유형의 블록을 사용해서 네트워크 표시기 UI를 제어할 수 있습니다. 예를 들어 업로드나 다운로드가 활발히 진행되는 경우에 색이 변하거나 특정 UI가 등장하도록 할 수 있습니다.

RLMSyncSession *session = [[RLMSyncUser currentUser] sessionForURL:realmURL];
void(^workBlock)(NSUInteger, NSUInteger) = ^(NSUInteger downloaded, NSUInteger downloadable) {
  if ((double)downloaded/(double)downloadable >= 1) {
    [viewController hideActivityIndicator];
  } else {
    [viewController showActivityIndicator];
  }
};
RLMProgressNotificationToken *token;
token = [session addProgressNotificationForDirection:RLMSyncProgressDirectionDownload
                                                mode:RLMSyncProgressReportIndefinitely
                                               block:workBlock];

// Much later...
[token stop];

현재 처리되지 않은 작업에 대한 프로그레스를 보고하도록 블록을 구성할 수도 있습니다. 이 경우 등록된 시점에 전송할 수 있는 바이트를 저장하고, 이 값과 관련된 프로그레스를 보고합니다. 전송된 바이트 수가 해당 초기 값 이상이 되면 블록이 자동으로 등록을 취소합니다. 이 타입의 블록을 사용해서 사용자가 로그인한 경우 최초로 다운로드되는 동기화 Realm을 추적하는 프로그레스 바를 제어하고, 로컬 복사본이 최신 상태가 되기까지의 시간을 알려줄 수 있습니다.

RLMSyncSession *session = [[RLMSyncUser currentUser] sessionForURL:realmURL];
RLMProgressNotificationToken *token;
void(^workBlock)(NSUInteger, NSUInteger) = ^(NSUInteger uploaded, NSUInteger uploadable) {
  double progress = ((double)uploaded/(double)uploadable);
  progress = progress > 1 ? 1 : progress;
  [viewController updateProgressBarWithFraction:progress];
  if (progress == 1) {
    [viewController hideProgressBar];
    [token stop];
  }
};
token = [session addProgressNotificationForDirection:RLMSyncProgressDirectionUpload
                                                mode:RLMSyncProgressForCurrentlyOutstandingWork
                                               block:workBlock];

접근 제어

Realm 모바일 플랫폼은 유연한 접근 제어 방식을 제공해서 어떤 사용자가 어떤 Realm 파일과 동기화할 수 있는지에 대한 권한을 허용할 수 있게 합니다. 예를 들어 여러 사용자가 동일한 Realm에 쓸 수 있는 협업 앱을 만들 수 있습니다. 또한 한 사용자가 만든 데이터를 읽기 퍼미션을 가진 여러 사용자와 공유하는 게시자/구독자 시나리오에도 활용할 수 있습니다.

Realm의 접근 수준, 즉 권한 제어는 세 가지 레벨로 분류됩니다.

  • mayRead Realm으로부터 사용자가 읽을 수 있는지를 표기합니다.
  • mayWrite Realm에 사용자가 쓸 수 있는지를 표기합니다.
  • mayManage 사용자가 Realm의 권한을 변경할 수 있는지 여부를 표기합니다.

권한을 명시적으로 바꾸지 않으면 Realm의 소유자만 접근할 수 있습니다. 예외는 관리자들입니다. 그들은 언제나 서버의 모든 Realm의 모든 권한을 가집니다.

mayRead 없이 mayWrite만 설정하는 것과 같은 쓰기 전용 권한은 현재 지원되지 않습니다.

접근 제어에 대한 자세한 내용은 Realm 오브젝트 서버 문서의 접근 제어 섹션을 참조하세요.

권한 검색

사용자가 접근할 수 있는 모든 Realm과 각 Realm의 접근 레벨을 얻으려면 -[RLMSyncUser retrievePermissionsWithCallback:] 메서드를 사용하세요.

[[RLMSyncUser currentUser] retrievePermissionsWithCallback:^(RLMSyncPermissionResults *permissions, NSError *error) {
  if (error) {
    // handle error
    return;
  }
  // success! access permissions
}];

권한 수정

Realm 파일에 대한 접근 제어 설정을 수정하려면 다음 두 가지 방법 중 하나를 수행합니다: 권한 값 적용/취소, 제공/응답 객체

권한 부여

Realm에 대한 접근을 직접 허용하거나 취소하도록 권한 값을 다른 사용자에게 적용할 수 있습니다.

RLMSyncPermissionValue *permission = [[RLMSyncPermissionValue alloc]
                                       initWithRealmPath:realmPath                 // The remote Realm path on which to apply the changes
                                                  userID:anotherUserID             // The user ID for which these permission changes should be applied
                                             accessLevel:RLMSyncAccessLevelWrite]; // The access level to be granted
[user applyPermission:permission callback:^(NSError *error) {
  if (error) {
    // handle error
    return;
  }
  // permission was successfully applied
}];

동기화 사용자가 관리하는 모든 Realm에 대한 권한 변경 사항을 적용하려면, realmPath 값을 *로 지정하세요. 오브젝트 서버에 허가된 모든 동기화 사용자에 대한 사용 권한 변경 사항을 적용하려면 userID 값을 *로 지정하세요.

권한 취소

권한 취소는 권한 값에 RLMSyncAccessLevelNone접근 레벨 값을 지정하거나 아무 권한 값이나 -[RLMSyncUser revokePermission:callback:]에 넘기는 것으로 수행할 수 있습니다. 두 메서드는 같은 효과를 냅니다.

권한 보내기와 응답

RLMSyncPermissionOfferRLMSyncPermissionOfferResponse클래스는 사용자 사이에 Realm을 공유할 수 있도록 합니다. 클라이언트 API에서 수행할 수 있고 서버 코드는 필요하지 않습니다.

권한 보내기는 관리 Realm에서 작성하는 것으로 생성하고 사용할 수 있습니다. 관리 Realm은 일반 동기 Realm처럼 쓰거나 읽을 수 있습니다. 다만 Realm 오브젝트 서버는 이 Realm에 대한 변경 사항에 반응하도록 특별히 설계됐습니다. 이 Realm에 권한 변경 객체를 추가해서 Realm 파일 접근 제어 설정을 수정할 수 있습니다.

특정 사용자의 관리 Realm을 얻으려면 -[RLMSyncUser managementRealmWithError:] 메서드를 호출하세요.

권한 보내기를 통해 Realm을 공유하려면 다음 단계를 수행하세요.

  1. 공유 사용자의 관리 Realm에 RLMSyncPermissionOffer 객체를 생성합니다.
  2. Realm 오브젝트 서버에서 권한 보내기가 동기화되고 처리될 때까지 기다립니다. 이 과정에서 offer 객체에 token 속성값이 채워집니다.
  3. 다른 유저에게 토큰을 보냅니다. 어떤 방법이든 괜찮습니다.
  4. 수신자가 자신의 관리 Realm에 RLMSyncPermissionOfferResponse를 생성합니다.
  5. 서버에서 권한 보내기에 대한 응답이 동기화되고 처리될 때까지 기다립니다. 이 과정에서 response 객체의 realmUrl 속성값이 채워집니다.
  6. 이제 수신자가 URL을 통해 공유 Realm에 접근할 수 있습니다.
RLMSyncPermissionOffer *shareOffer = [RLMSyncPermissionOffer permissionOfferWithRealmURL:realmURL
                                                                               expiresAt:nil
                                                                                    read:YES
                                                                                   write:YES
                                                                                  manage:YES];

// Add to management Realm to sync with ROS
[managementRealm transactionWithBlock:^{
  [managementRealm addObject:shareOffer];
}];

// Wait for server to process
RLMNotificationToken *shareOfferNotificationToken = [shareOffer addNotificationBlock:^(BOOL deleted, NSArray<RLMPropertyChange *> *changes, NSError *error) {
  if (deleted == NO && error == nil && shareOffer.status == RLMSyncManagementObjectStatusSuccess && shareOffer.token) {
    // Send `token` to the other user
  }
}];

권한 값 모델과 마찬가지로 인자를 통해 제공된 realmURLread, write, , manage 권한을 제어할 수 있습니다. expiresAt 인자는 토큰이 더 이상 사용가능하지 않을 시점을 제어합니다. expiresAt 값을 넘기지 않거나 nil을 지정하면 권한 보내기는 만료되지 않습니다. 한번 권한 보내기 토큰을 처리한 사용자는 만료 시점이 지나도 권한을 잃지 않는다는 점을 주의하세요.

다른 사용자가 토큰을 얻으면 이를 사용해서 RLMSyncPermissionOfferResponse 객체를 만듭니다.

// Create response with received token
RLMSyncPermissionOfferResponse *response = [RLMSyncPermissionOfferResponse permissionOfferResponseWithToken:token];

// Add to management Realm to sync with ROS
[managementRealm transactionWithBlock:^{
  [managementRealm addObject:response];
}];

// Wait for server to process
RLMNotificationToken *acceptShareNotificationToken = [response addNotificationBlock:^(BOOL deleted, NSArray<RLMPropertyChange *> *changes, NSError *error) {
  if (deleted == NO && error == nil && response.status == RLMSyncManagementObjectStatusSuccess && response.realmUrl) {
    // User can now access Realm at `response.realmUrl`
  }
}];

RLMSyncPermissionOffer로 부여된 권한은 부가적입니다. 즉, 이미 write 접근 권한이 있는 사용자가 read 권한을 부여받아도 쓰기 권한이 사라지지 않습니다.

권한 보내기는 관리 Realm에서 RLMSyncPermissionOffer를 삭제해거나 expiresAt 속성을 과거의 날짜로 설정해서 취소할 수 있습니다. 이런 경우 새 사용자는 권한 보내기를 수락하지 못하지만 이미 offer를 사용해서 권한을 획득한 사용자의 사용 권한은 취소되지 않습니다.

로그

동기화 서브시스템은 앱 개발에 유용한 여러 레벨의 로그를 지원하며, RLMSyncManager 싱글턴의 logLevel 속성에 원하는 로그 길이를 설정해 줄 수 있습니다.

[[RLMSyncManager sharedManager] setLogLevel:RLMSyncLogLevelOff];

로그 레벨은 반드시 동기 Realm이 열리기 전에 설정돼야 합니다. 동기 Realm을 한 번 연 이후에 변경하면 효과가 없습니다.

로그 레벨에 대한 자세한 내용은 Realm 오브젝트 서버 설정 문서에서 볼 수 있습니다.

에러 리포트

특정 동기화 관련 API를 통해 실패할 수 있는 비동기 작업을 수행할 수 있습니다. 이러한 API는 에러 매개 변수를 허용하는 완료 블록을 사용하며, 작업이 실패하는 경우 에러 매개 변수가 전달됩니다. 에러에서는 자세한 내용을 확인할 수 있습니다.

RLMSyncManager 싱글턴에 에러 핸들러를 설정하는 것을 강력히 추천 합니다. 전역 동기화 서브시스템이나, 서버와 동기화를 위해 열린 Realm을 나타내는 특정 세션과 관련된 오류는 이 에러 핸들러를 통해 보고됩니다. 오류가 발생하면 에러 핸들러는 에러를 표현하는 객체와, 해당되는 경우 에러가 발생한 세션을 나타내는 RLMSyncSession과 함께 호출됩니다.

[[RLMSyncManager sharedManager] setErrorHandler:^(NSError *error, RLMSyncSession *session) {
  // handle error
}];

에러

Realm 모바일 플랫폼 에러는 도메인이 RLMSyncErrorDomainNSError 객체로 표현됩니다. 에러 코드와 그 의미에 대해서는 RLMSyncErrorRLMSyncAuthError의 정의를 참고하세요.

클라이언트 재설정

Realm 객체 서버에 손상이 발생하고 백업에서 복원해야 하는 경우, 동기화 Realm의 클라이언트 재설정 을 앱에서 수행해야 할 수 있습니다. 이는 문제가 되는 Realm의 로컬 버전이 서버의 동일 Realm 버전보다 클 때 발생합니다. 예를 들어 Realm 오브젝트 서버의 백업 이후 애플리케이션이 변경 사항을 만들었지만, 이 변경 사항이 서버 손상 이전에 동기화되지 않았기 때문에 복원이 필요한 경우입니다.

클라이언트를 재설정하는 절차는 다음과 같습니다. 로컬 Realm 파일의 백업 사본이 만들어지면 Realm 파일이 삭제됩니다. 다음에 앱이 Realm 오브젝트 서버에 접속하고 Realm을 열면 새로운 사본이 다운로드됩니다. Realm 오브젝트 서버가 백업된 이후에 변경돼서 아직 서버로 동기화되지 않은 내용은 Realm의 백업 복사본에 보존되지만, Realm을 재다운로드하는 경우에는 존재하지 않습니다.

클라이언트 재설정 필요 사항은 RLMSyncManager 에러 핸들러로 전달되는 에러인 코드 RLMSyncErrorClientResetError 로 표시됩니다.

에러 객체는 또한 다음 두 값을 포함합니다. 클라이언트 재설정 프로세스가 수행된 후의 Realm 파일 백업 사본 위치와, 인수가 없는 불투명 블록 값입니다. 불투명 블록 값은 클라이언트 재설정 프로세스를 시작하기 위해 호출될 수 있습니다.

클라이언트 재설정 프로세스를 수동으로 시작하기 위해 블록을 호출하는 경우, 블록을 호출하기 전에 문제가 있는 모든 Realm 인스턴스를 반드시 무효화하고 없애야 합니다. 모든 참조를 nil로 만들더라도 자신을 포함한 오토릴리즈 풀이 고갈되기 전까지는 RLMRealm가 완전히 무효화되지 않을 수 있다는 점을 주의하세요. 문제가 있는 모든 Realm 인스턴스를 제대로 없앤 다음에는 클라이언트 재설정 프로세스가 완료된 후 Realm을 즉시 다시 열 수 있으므로 다시 동기화를 시작할 수 있습니다.

블록이 호출되지 않은 경우, 클라이언트 재설정 프로세스는 다음에 앱이 시작될 때 자동으로 실행됩니다. 처음에는 RLMSyncManager 싱글턴에 접근합니다. 필요한 경우 백업 복사본의 위치를 유지하여 나중에 찾을 수 있도록 앱에서 관리해야 합니다.

재설정이 필요한 Realm을 계속해서 읽고 쓸 수는 있지만, 클라이언트 재설정이 완료되고 Realm이 다시 다운로드될 때까지 이후 변경 사항이 서버에 동기화되지 않습니다. 따라서 애플리케이션이 클라이언트 재설정 에러를 관찰하거나, 최소한 재다운로드된 Realm 복사본에 쓸 수 있도록 클라이언트 재설정이 유발된 후 새로 만들어지거나 수정된 사용자 데이터를 저장해야 합니다.

백업 복사본 파일 경로는 NSError 객체에 -[NSError rlmSync_clientResetBackedUpRealmPath]를 호출해서 얻을 수 있습니다. userInfo 딕셔너리에 kRLMSyncPathOfRealmBackupCopyKey 키를 사용해서 직접 얻을 수도 있습니다.

재설정 초기화 블록은 NSError 객체에 대해 -[NSError rlmSync_clientResetBlock]을 호출해서 얻을 수 있습니다. 또한 userInfo 딕셔너리에 kRLMSyncInitiateClientResetBlockKey 키를 사용해서 직접 얻을 수도 있습니다.

다음은 클라이언트 재설정 API를 사용해서 클라이언트 재설정을 수행하는 예제입니다.

[[RLMSyncManager sharedManager] setErrorHandler:^(NSError *error, RLMSyncSession *session) {
  if (error.code == RLMSyncErrorClientResetError) {
    [realmManager closeRealmSafely];
    [realmManager saveBackupRealmPath:[error rlmSync_clientResetBackedUpRealmPath]];
    [error rlmSync_clientResetBlock]();
    return;
  }
  // Handle other errors...
}];

Realm 오브젝트 서버가 클라이언트 재설정을 처리하는 방법에 대한 더 많은 정보는 서버 문서를 참조하세요.

동기 마이그레이션

마이그레이션은 동기 Realm에 자동으로 적용되나 몇 가지 제한 사항과 주의 사항이 있습니다.

  • 클래스를 추가하거나 기존 클래스에 속성을 추가하는 것과 같이 추가적인 변화은 자동으로 적용됩니다.
  • 속성을 스키마에서 제거하면 데이터베이스의 필드에서는 삭제되지 않지만 Realm이 해당 속성을 무시하도록 지시할 수 있습니다. 새로운 객체는 해당 속성을 계속 만들지만 값이 null로 설정됩니다. nullable이 아닌 필드는 숫자 필드에는 0, 문자열 속성에는 빈 문자열이 들어가는 등 적절하게 0이나 빈 값으로 설정됩니다.
  • 사용자 지정 마이그레이션 블럭은 동기 Realm 마이그레이션에서 호출할 수 없으며, 호출 시 예외가 발생합니다.
  • 해당 Realm과 상호 작용하는 코드를 변경해야 하는 Realm 스키마 변경과 같은 파괴적인 변경은 직접 지원되지 않습니다. 예를 들어 nullable 문자열에서 nullable이 아닌 문자열로 변경하는 것과 같이 같은 이름을 사용하는 속성을 변경하는 경우나, 기본 키를 변경하거나, 필드를 옵셔널에서 필수, 혹은 그 반대로 변경하는 경우와 같은 변경은 지원되지 않습니다.

클라이언트에서 알림 핸들러를 작성해서 변경하거나, Node.js SDK를 이용할 수 있는 오브젝트 서버 에디션을 사용하는 경우 서버의 JavaScript 함수를 사용해서 사용자 정의 마이그레이션을 수행할 수 있습니다. 다만 마이그레이션이 파괴적인 변경을 하는 경우 Realm은 ROS와의 동기화를 중단하고 Bad changeset received를 생성합니다.

동기 Realm 마이그레이션에서 파괴적인 변경을 수행하려면 새 스키마로 새로운 동기 Realm을 만들고 기존 Realm의 변경을 수신해서 새 Realm으로 값을 복사하는 마이그레이션 함수를 작성하세요. 앞서 말한 비파괴적인 마이그레이션과 마찬가지로 이 기능은 앱의 새 버전 클라이언트의 코드나 서버의 Node.js 코드에서 수행할 수 있습니다.

로컬 Realm을 동기화 Realm으로 변환

동기화되지 않은 로컬 Realm을 동기화 Realm으로 자동 변환하는 기능은 아직 지원하지 않으며, 추후에 지원할 계획입니다. 로컬 Realm을 동기화 Realm으로 변환할 계획이라면, 새 동기화 Realm을 열어서 로컬 Realm의 객체를 수동으로 복사해야 합니다.

충돌 해결

Realm 오브젝트 서버 문서에서 충돌 해결 지원에 관한 내용을 볼 수 있습니다.

FAQ

Realm 라이브러리는 얼마나 큰가요?

Realm은 기존 앱 다운로드 크기에서 오직 5 ~ 8MB 정도만 추가합니다. 현재 배포되고 있는 Realm 라이브러리는 iOS, watchOS, tvOS 시뮬레이터를 지원하고 디버그 심볼, Bitcode을 포함해 상당히 커지고 있습니다. 앱을 빌드하게 되면 App Store에 의해 자동적으로 제거됩니다.

상용 어플리케이션에도 Realm을 사용할 수 있나요?

Realm은 2012년부터 상용 제품에 사용되었습니다.

Realm의 Objecitve-C와 Swift API가 커뮤니티의 피드백과 함께 더 많은 기능과 개선 사항으로 개선되고 있다고 기대하셔도 됩니다.

Realm을 사용하기 위해 비용을 지불해야 하나요?

아닙니다. Realm은 상용 프로젝트를 포함하여 사용이 무료입니다.

어떻게 수익을 낼 계획입니까?

이미 기술을 중심으로 기업제품과 서비스를 판매하여 수익을 창출하고 있습니다. 만약 릴리즈나 realm-cocoa보다 현재상태에 대해 더욱 알고 싶으시다면, 이메일로도 대화를 할 수 있습니다. 그리고 공개적인 realm-cocoa를 개발하기 위해, 또한 무료 및 Apache 2.0 라이센스 하에 오픈소스로 유지 될 수 있도록 최선을 다하고 있습니다.

코드 안에 “core”로 참조되어 있는 것을 보았습니다. 이것은 무엇인가요?

core는 내부 C++ 스토리지 엔진의 이름입니다. 그 코어는 현재 오픈 소스가 아니지만 내부를 정리하고 이름을 정리하고 주요 기능을 정하면 역시 Apache 2.0 라이센스로 오픈소스화를 할 계획입니다. 또한, 해당 바이너리는 Realm Core(TightDB)의 바이너리 라이센스 내에서 사용할 수 있습니다.

앱을 실행하면서 Mixpanel로의 네트워크 호출을 보았습니다. 이것은 무엇인가요?

디버깅 모드로 혹은 시뮬레이터에서 앱을 실행할 때 Realm은 익명 분석 정보를 수집합니다. 이 정보는 전적으로 익명이며 Realm, iOS, OS X의 어떤 버전, 혹은 어떤 언어를 타깃했는지에 대한 정보를 바탕으로 저희 제품을 개선하고 버전 지원을 중단할지 결정하는데 도움이 됩니다. 프로덕션 모드 혹은 사용자 기기에서는 이 호출이 실행되지 않습니다. - 디버깅 모드나 시뮬레이터일 때만 동작합니다. 소스 코드에서 어떤 정보를 어떻게 수집하는지와 수집 이유가 무엇인지 정확히 공개하고 있습니다.

트러블 슈팅

크래시 리포팅

애플리케이션의 크래시 리포터 사용을 권장합니다. 다른 디스크 I/O처럼 Realm 오퍼레이션 역시 런타임 동안 동작 실패가 발생할 수 있으므로, 수집된 크래시 리포트는 문제 영역을 식별하고 에러 처리를 개선하며 크래싱 버그를 수정하는데 도움이 됩니다.

대부분의 상용 크래시 리포터는 로그 수집 옵션이 있으며, 이 기능을 사용하시는 것을 강력히 추천합니다. Realm은 예외나 회복 불가능한 상태에 발생할 경우 사용자 데이터를 제외한 메타데이터 정보를 로깅하며, 이 메시지는 에러 발생 시 디버깅에 도움이 됩니다.

Realm 이슈 보고

Realm과 관련된 이슈를 발견할 경우 해당 이슈를 식별하고 재현할 수 있도록 가능한 많은 정보와 함께 GitHub으로 이슈를 제기하거나 help@realm.io로 메일을 보내주세요.

다음과 같은 정보가 매우 유용합니다.

  1. 목표
  2. 예상 결과
  3. 실제 결과
  4. 재연에 필요한 단계
  5. 이슈 중 가장 중요한 부분의 코드 샘플 (우리가 직접 컴파일할 수 있는 전체 Xcode 프로젝트가 가장 이상적입니다.).
  6. Realm / Xcode / OS X 버전
  7. 관련 디펜던시 매니저 버전 (CocoaPods / Carthage).
  8. 버그 발생시의 플랫폼, OS 버전, 구조 (예. 64-bit iOS 8.1).
  9. 크래시 로그와 스택 트레이스. 자새한 내용은 상단의 크래시 리포팅을 참고하세요.

디펜던시 매니저로 재설치

Realm을 CocoaPods나 Carthage로 설치한 후 빌드 에러를 겪고 있다면 해당 디펜던시 매니저의 미지원 버전을 사용했거나, 프로젝트로의 Realm 통합이 실패했거나, 빌드 툴이 오래된 캐시를 가지고 있을 수 있습니다.

이런 경우 디펜던시 매니저 폴더를 지우고 다시 생성 및 설치를 해주세요.

관련 데이터를 지우기 를 하고 Xcode의 빌드 폴더를 클린 을 시도할 수 있습니다. 빌드 툴 버전을 고치거나 새로운 타깃이 추가하는 등 프로젝트 설정을 바꾸는 것으로 이 문제를 고칠 수 있습니다. 빌드 폴더를 클린하기 위해서는 ‘Product’ 메뉴를 여는 동안 ‘Option’ 키를 누르고 있고 ‘Clean Build Folder…‘를 선택합니다. Xcode의 도움 검색 메뉴에서 “Clean”을 입력하고 검색 결과가 떴을 때 ‘Clean Build Folder…’ 메뉴 항목을 선택할 수도 있습니다.

CocoaPods

Realm은 CocoaPods 0.39.0 이상으로 설치할 수 있습니다.

CocoaPods 통합에 어려움을 겪고 있다면 통합 단계를 리셋해서 해결될 수 있습니다. 터미널에서 아래 커맨드를 프로젝트의 상위 디렉터리에서 입력하세요.

pod cache clean Realm
pod cache clean RealmSwift
pod deintegrate || rm -rf Pods
pod install --verbose
rm -rf ~/Library/Developer/Xcode/DerivedData

Pods 디렉터리를 삭제하는 대신 cocoapods-deintegrate를 사용해도 됩니다. CocoaPods 1.0 부터 이 플러그인이 함께 설치됩니다. 오래된 버전을 이용한다면 gem install cocoapods-deintegrate 명령어로 설치할 수 있습니다. 실행은 pod deintegrate 명령어로 합니다. 이 경우 Xcode 프로젝트에서 CocoaPods의 모든 흔적이 삭제됩니다.

Carthage

Realm은 Carthage 0.9.2 이상으로 설치할 수 있습니다.

프로젝트로부터 모든 Carthage의 관리 디펜던시를 지우려면 터미널에서 아래 커맨드를 프로젝트의 상위 디렉터리에서 입력하세요.

rm -rf Carthage
rm -rf ~/Library/Developer/Xcode/DerivedData
carthage update

Realm 코어 바이너리 다운로드 실패

Realm을 구성할 때 스태틱 바이너리로 코어 라이브러리를 다운로드받고 realm-cocoa 프로젝트로 통합하는 과정을 거칩니다.

특정 경우 코어 바이너리가 다음과 같은 에러 메시지와 함께 다운로드에 실패하는 경우가 보고되고 있습니다.

Downloading core failed. Please try again once you have an Internet connection.

이 에러는 다음과 같은 상황에서 발생합니다.

  1. United States embargoes 리스트 상의 IP 주소를 사용할 경우. 미국 법률에 따라 Realm은 해당 지역에서의 사용이 불가능합니다. 자세한 정보는 라이센스를 확인하세요.
  2. 중국 본토에 있으며 국가별 방화벽으로 인해 CloudFlare나 Amazon AWS S3 서비스에 일시적으로 접근할 수 없는 경우 자세한 정보는 Realm-Cocoa 이슈를 확인하세요.
  3. Amazon AWS S3가 서비스되지 않을 때. AWS Service Health Dashboard를 확인하고 다음에 다시 시도하세요.

낮은 메모리 제약 조건으로 작동

watchOS이나 앱 익스텐션같이 사용 가능한 메모리가 거의 없는 환경에서 Realm을 동작하려는 경우 objc_copyClassList()에 대한 고비용 호출을 피하기 위해 Realm이 관리할 클래스를 명시적으로 지정하는 것이 좋습니다.

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.objectClasses = @[Dog.class, Person.class];
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];