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

순수하게 Swift 에서 Realm을 사용하는 것을 찾고 있다면, Realm Swift를 사용하는 것을 고려해야합니다. Realm Objective‑C 와 Realm Swift 를 동시에 사용하는 것은 지원하지 않습니다.

Realm 는 효율적으로 안전하고, 지속되고, 빠른 방법으로 앱의 model layer를 작성할 수 있게 합니다. 아래의 예를 참조하세요.

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

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

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

// Query it from any thread
dispatch_async(dispatch_queue_create("background", 0), ^{
  RLMResults<Dog *> *r = [Dog objectsWhere:@"age > 8"];
});

현재 Core Data를 사용하고 Realm으로 전환을 고려하고 있는 앱이 있다면, 우리는 어떻게 이를 해결할 지를 논의한 기사를 발표했습니다. 관련 기사

시작하기

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

요구사항

  • iOS 7 이상 또는 OS X 10.9 이상 및 watchOS.
  • Xcode 6 이상.
  • Objective‑C와 Swift 1.2 Swift 2.0을 지원하고 있습니다.

설치

  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++.dylib 를 추가합니다.
  4. Swift와 함께 사용한다면 Swift/RLMSupport.swift 파일을 Xcode의 File Navigator에 넣으신 후 Copy items if needed 를 선택합니다.

주의 : 동적 프레임워크는 오직 OS X 또는 iOS 8 과 그 상위버전 프로젝트만 호환이 됩니다.

  1. Realm의 최신버전을 다운로드하고, zip 압축을 풉니다.
  2. Xcode 프로젝트에서 “General” 설정으로 이동합니다. ios/dynamic/ 또는 osx/ 디렉터리에서 “Embedded Binaries” 부분으로 Realm.framework 를 옮깁니다. Copy items if needed 를 선택했는지 확인하고 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 8 프로젝트에서 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.37.1 혹은 상위 버전을 설치하세요.
  2. In your Podfile, add pod 'Realm' to your app target and pod 'Realm/Headers' to your test target.
  3. Podfile에 pod ‘Realm’을 앱 타깃에 추가하고 pod ‘Realm/Headers’를 테스트 타깃에 추가하세요.
  4. 커맨드라인을 통해 pod install을 실행하세요.
  5. 프로젝트에서 사용하기 위해서는 CocoaPods이 생성한 .xcworkspace을 사용하세요.
  1. Carthage 0.7.5 혹은 상위버전을 설치하세요.
  2. github "realm/realm-cocoa"를 Cartfile에 추가합니다.
  3. carthage update를 실행합니다.
  4. iOS : Xcode 프로젝트의 “General” 셋팅에서 “Linked Frameworks and Libraries” 부분에 Carthage/Build/iOS/ 디렉터리로 Realm.framework 를 옮깁니다. OS X: Xcode 프로젝트의 “General” 셋팅에서 “Embedded Binaries” 부분에 Carthage/Build/Mac/ 디렉터리로 Realm.framework 를 옮깁니다.
  5. iOS: 앱 타깃의 “Build Phases” 셋팅 탭에서 “+” 아이콘을 누르고 “New Run Script Phase” 선택합니다. 다음과 같이 실행 스크립트를 생성합니다:
    /usr/local/bin/carthage copy-frameworks
  6. iOS: On your application targets’ “Build Phases” settings tab, click the “+” icon and choose “New Run Script Phase”. Create a Run Script with the following contents:

    /usr/local/bin/carthage copy-frameworks

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

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

    이 스크립트는 universal binaries 에 의해 App Store submission bug 를 해결해야합니다.

Realm 브라우저

또한 Realm 데이터베이스를 읽고 수정할 수 있는 Realm 브라우저 맥 앱을 제공하고 있습니다.

Realm Browser

Tools > Generate demo database 메뉴를 통해서 테스트용 데이터베이스와 더미 데이터를 생성할 수 있습니다.

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

Realm 브라우저는 Mac App Store에서 이용가능합니다.

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의 여러 기능 사용법을 포함하고 있습니다.

도움을 얻으려면

모델

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

만약 이미 제공 중인 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이 코드에 명시된 모든 모델을 분석하므로 사용하지 않는 모델이더라도 문법적으로 유효해야 합니다.

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

지원하는 데이터 유형

Realm이 지원하는 속성 타입은 다음과 같습니다: BOOL, bool, int, NSInteger, long, long long, float, double, CGFloat, NSString, NSDate 초단위, NSData.

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 하는지 알려주게 합니다.

관계

RLMObject는 RLMObject 와 RLMArray 속성 사용으로 서로 연결할 수 있습니다. RLMObject는 NSArray와 매우 유사한 인터페이스를 제공하고 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와 유사한 인터페이스를 제공합니다.

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

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

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

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];

역관계

역관계(backlinks로도 알려진)로 특정 속성을 통해 주어진 객체와 연결된 모든 객체를 조회할 수 있습니다. 예를 들어, Dog 객체에서 -linkingObjectsOfClass:forProperty: 를 호출하면 지정한 속성과 특정 클래스에 연결된 모든 객체를 반환합니다. Dog에 읽기 전용(computed) owners 속성을 정의하여 패턴을 단순화할 수 있습니다:

@interface Dog : RLMObject
@property NSString *name;
@property NSInteger age;
@property (readonly) NSArray *owners; // Realm doesn't persist this property because it is readonly
@end

@implementation Dog
// Define "owners" as the inverse relationship to Person.dogs
- (NSArray *)owners {
    return [self linkingObjectsOfClass:@"Person" forProperty:@"dogs"];
}
@end

속성 특성

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

인덱스 속성

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

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

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

기본값

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

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

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

기본키

클레스 메소드의 +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

쓰기

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

Realm 객체는 보통의 객체와 같이 단독적으로 값을 지정하고 사용이 가능합니다. 쓰레드 사이에 객체를 공유하거나 실행 중인 앱 간에 재사용을 하기 위해서는 쓰기 트랜잭션 내에서 수행되는 작업으로 Realm에 객체를 저장해야만 합니다.

객체 생성

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 *aPerson = [[Person alloc] initWithValue:@[@"Jane", @30, @[aDog, anotherDog]]];

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

이는 중첩 배열 및 Dictionary의 조합을 위해 작동할 것 입니다. RLMArray 은 RLMObject만 포함하고, 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 설계 덕분에 쓰기 트랜잭션이 실행 중에는 읽기는 제한되지 않습니다. 한 번에 여러 쓰레드에서 동시 쓰기를 해야하지 않는 이상, 대량의 쓰기 트렌잭션으로 구성하기 보다는 다수의 세세한 쓰기 트랜잭션으로 구성하길 선호하실 겁니다.

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

객체 업데이트

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

입력 업데이트

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

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

기본 키 객체를 업데이트

모델에 기본 키가 있다면, 객체를 업데이트 할 수 있거나 +[RLMObject createOrUpdateInRealm:withValue:]을 사용해 아직 존재하지 않을 경우에 새 번호를 추가할 수 있습니다.

// 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];
[Book createOrUpdateInRealm:realm withValue:cheeseBook];
[realm commitWriteTransaction];

기본 키 id의 1인 책이 데이터베이스에 없다면, 이 대신에 새로운 책을 만들 수 있습니다.

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

// 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];

Key-Value Coding (KVC)

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

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

RLMResults *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:] 메서드에 전달하세요.

Book *cheeseBook = ... // Book 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 인스턴스를 반환합니다. RLMResults 는 NSArray와 매우 유사한 인터페이스를 제공하고 RLMResult에 포함된 객체는 인덱스 첨자로 접근할 수 있습니다. NSArray 와 달리, RLMResults 는 타입을 강제하고 하나의 RLMObject 서브클래스를 갖도록만 설계되었습니다.

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

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

타입을 통한 객체 검색

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

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

// Query a specific Realm
RLMRealm *petsRealm = [RLMRealm realmWithPath:@"pets.realm"]; // get a specific Realm
RLMResults *otherDogs = [Dog allObjectsInRealm:petsRealm]; // retrieve all Dogs from that Realm

조건 쿼리

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

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

// Query using a predicate string
RLMResults *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];

애플의 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’

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

예. name CONTAINS[c] ‘Ja’ 알림. “A-Z”, “a-z”만이 대소문자 구분에서 제외됩니다.

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

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

  • 포함 연산자 IN.

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

  • Nil 비교 ==, !=.

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

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

  • ANY 비교.

예. ANY student.age < 21

  • 알림, 집합 표현 타입을 지원하지는 않지만 객체 값을 이용한 BETWEEN 연산자를 지원합니다.

예. [Person objectsWhere:@"age BETWEEN %@", @[42, 43]]

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

결과값 정렬

RLMResults는 하나 혹은 여러 개의 속성으로 정렬 기준이나 순서를 설정하도록 지원합니다. 예를 들어, 아래는 결과값을 이름 알파벳순으로 정렬하는 코드입니다.

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

[RLMObject objectsWhere:][RLMResults sortedResultsUsingProperty:ascending:]을 통해 상세 내용을 확인하세요.

연속 조회

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

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

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

Realm

기본 Realm

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

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

Realm 설정 (Configuration)

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

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

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

  // Use the default directory, but replace the filename with the username
  config.path = [[[config.path stringByDeletingLastPathComponent]
                   stringByAppendingPathComponent:username]
                   stringByAppendingPathExtension:@"realm"];

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

다른 Realm

간혹 여러 개의 Realm을 두는게 유용할 수도 있습니다. 어플리케이션과 함께 일부 데이터를 메인 Realm뿐만 아니라 Realm 파일에 모아놓을 필요가 있을지도 모른다. 다음과 코드를 사용하여 이 작업을 수행 할 수 있습니다:

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];

// Get the path to the bundled file
config.path = [[NSBundle mainBundle] pathForResource:@"MyBundledData" ofType:@"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];

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

지정 경로가 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];

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

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

주의: In-memory Realm의 대한 참조가 범위를 벗어나게 되면 모든 데이터는 메모리 해제됩니다. 앱이 구동되는 동안 In-memory Realm에 메모리 참조를 하도록 추천합니다.

하나의 Realm을 쓰레드 간에 공유하여 사용

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

쓰레드 간에 Realm 인스턴스 공유를 지원하지 않습니다. 같은 Realm 파일에 접근하는 Realm 인스턴스는 모두 같은 readOnly 값이어야 합니다. (혹은 모두 같은 readwrite이거나 모두 readonly).

하나의 트랜잭션 내에서 동시에 여러 쓰기를 일괄 처리하여 많은 양의 데이터를 기록 할 때 Realm은 상당히 효율적입니다. 트랜잭션은 또한 메인 쓰레드를 블록킹하지 않도록 Grand Central Dispatch를 사용하여 백그라운드로 수행할 수 있습니다. RLMRealm 객체는 쓰레드로부터 안전하지 않은 쓰레드를 통해 공유 할 수 없습니다. 그래서 읽거나 쓰고자 하는 각 쓰레드/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"      : [self randomString],
                                @"birthdate" : [self randomDate]}];
      }

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

Realm 간에 객체 복사하기

원본 객체를 +[RLMObject createInRealm:withValue:] 에 넘겨주는 것만으로 다른 Realm에 Realm 객체를 손쉽게 복사할 수 있습니다. 예를 들어, [MyRLMObjectSubclass createInRealm:otherRealm withValue:originalObjectInstance].

Realm 파일 찾기

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

Realm과 앱을 같이 배포하기

초기 데이터와 함께 앱을 배포하는 건 흔한 일이고 초기 실행 때 가능하게 하는 방법이 여기 있습니다:

  1. 우선, Realm을 옮깁니다. 같은 데이터 모델을 사용해야 하고 Realm을 생성하고 해당 앱에 초기화하길 원하는 데이터를 담은 후 배포합니다. Realm 파일은 플랫폼 간에 호환이 가능하기 때문에 OS X 앱(JSONImport 예제)(https://github.com/realm/realm-cocoa/tree/master/examples/osx/objc/JSONImport)이나 시뮬레이터에 띄워진 앱을 사용할 수 있습니다.
  2. Realm 파일을 생성한 코드에서 해당 파일의 압축된 복사본을 만들어야 합니다. (-[RLMRealm writeCopyToPath:error:]). 해당 메소드는 Realm 파일 크기를 줄이고 사용자에게 전달될 최종 버전을 경량화 시킵니다.
  3. 새로 생성된 해당 복사본을 최종 앱의 Xcode Project Navigator에 마우스로 끌어 추가합니다.
  4. Xcode에서 앱 타깃의 Build Phases 탭으로 간 후 Realm 파일을 “Copy Bundle Resources”에 추가합니다.
  5. 이때, 같이 배포된 Realm 파일은 접근 가능해집니다. [[NSBundle mainBundle] pathForResource:ofType:] 메소드 사용으로 파일 경로를 확인할 수 있습니다.
  6. 번들 Realm에 수정할 필요 없는 고정된 데이터가 있는 경우에, RLMRealmConfiguration 객체에 readOnly = true 를 설정하여 번들 경로에서 직접 열 수 있습니다. 그외에는, 초기 데이터가 수정이 된다면, [[NSFileManager defaultManager] copyItemAtPath:toPath:error:] 을 사용하여 Documents 폴더에 배포된 파일을 복사할 수 있습니다.

배포된 Realm 파일을 어떻게 다루는지 예제를 보고 싶다면 마이그레이션 예제 앱에서 확인하세요.

Class Subsets

일부 시나리오에서는 특정 Realm에 저장 될 수 있는 클래스를 제한 할 수 있습니다. 예를 들어, 내부적으로 Realm을 사용하는 어플리케이션의 다른 요소에서 작업하는 두 팀이 있다면, 그들 사이의 마이그레이션 을 조정하고 싶지 않을 수도 있습니다. RLMRealmConfiguration의 objectClasses 속성을 설정하여 작업을 수행할 수 있습니다:

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

알림

Realm 객체는 쓰기 트랜잭션이 커밋될 때마다 여러 쓰레드의 다른 객체에 알림을 보냅니다. 이러한 알림은 블록을 등록하여 구독할 수 있습니다:

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

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

보다 자세한 내용은 [RLMRealm addNotificationBlock:][RLMRealm removeNotificationBlock:] 을 확인하세요.

마이그레이션

다른 어떤 데이터베이스로 작업할 때와 마찬가지로 데이터 모델은 시간이 지남에 따라 변경됩니다. 표준 클레스로 Realm의 데이터 모델을 정의하고 다른 클레스를 수정하는 것처럼 모델을 수정하기 간편합니다. 예를 들어, 아래와 같은 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

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

마이그레이션 실행

마이그레이션을 정의하고 +[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 및 모든 개체를 평소와 같이 접근 할 수 있습니다.

버전 추가

두 버전의 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 사용에 대한 제한이 있을 수 있으므로, 저희 라이센스의 수출 규정 준수 조항을 참고하십시오.

iOS에서 Realm 파일을 약간의 오버헤드를 감수하고 NSFileProtection API 사용으로 암호화할 수 있습니다. 암호화에 대한 두가지 주요 주의점입니다. 1) Realm 파일을 플랫폼 간에 이식하지 않는다. (NSFileProtection은 iOS 전용입니다.) 그리고 2) Realm 파일을 iOS 장비 내에서 사용자 암호로 암호화하지 않는다. 두가지 제한을 피하기 위해서는 (혹은 OS X 앱을 개발하고 있다면) Realm 암호화를 사용해야 합니다.

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

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

// Open the encrypted Realm file
RLMRealmConfiguration *config = [RLMRealm defaultConfiguration];
config.encryptionKey = key;
NSError *error;
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);
    return;
}

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

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

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

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

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

Key-Value Observation

Realm 객체는 대부분의 속성에 대한 Key-Value Observing을 대응하고 있습니다. RLMObject 서브 클래스의 모든 지속 (무시가 아닌) 속성들은 RLMObject와 RLMArray에 무효화된 속성들과 함께 KVO를 대응합니다.

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

지속된 객체에 대한 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 를 변경하는 메소드를 호출하면 (모니터링하고 있으면) 업데이트가 통지됩니다.

샘플 코드에 간단한 Realm와 ReactiveCocoa (Objective‑C) 의 샘플 및 ReactKit (Swift) 의 샘플이 있으므로 참고하세요.

디버깅

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

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

Xcode 스크린샷

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

암호화된 Realm 디버깅하기

암호화 된 Realm을 사용하는 프로세스에 LLDB 세션을 연결하는 것은 현재 지원되지 않습니다. REALM_DISABLE_ENCRYPTION = YES 환경 변수를 설정하여이 문제를 피할 수 있습니다. 코드를 변경하지 않고 디버깅을 가능하게하기 위해이 환경 변수는 강제로 암호화 API를 비활성화합니다. 물론 보안을 유지하기 위해 이미 암호화 된 .realm 파일에 대해서는 적용되지 않습니다 ( File :: AccessError 예외가 발생합니다). 그러나 새로운 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
+ (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:nil error:nil];
  [realm transactionWithBlock:^{
    [User createOrUpdateInRealm:realm withValue:object];
  }];
}

// Test Code
- (void)testThatUserIsUpdatedFromServer {
  RLMRealm *testRealm = [RLMRealm realmWithPath:kTestRealmPath];
  NSData *jsonData = [@"{\"email\": \"help@realm.io\"}"
                      dataUsingEncoding:NSUTF8StringEncoding];
  [ClassBeingTested createOrUpdateUserInRealm:testRealm withData:jsonData];
  User *expectedUser = [User new];
  expectedUser.email = @"help@realm.io";
  XCTAssertEqualObjects([User allObjectsInRealm:testRealm][0],
                        expectedUser,
                        @"User was not properly updated from server.");
}

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

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

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

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

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

테스트하려는 모든 코드가 유닛테스트 타깃에 노출되도록 확인하세요. (Swift 1.2에서는 public 한정자를 사용, Swift 2.0 @testable 을 사용합니다.)

REST API

Realm은 손쉽게 REST API와 연동되고 지역 지속성없이 REST API를 사용하는 경우와 달리 몇가지 장점을 갖습니다.

  • REST API는 항상 연결되어야 하는 반면에 Realm 내부에 데이터를 캐싱하면 연결이 끊어져 있어도 오프라인 경험을 제공할 수 있습니다.
  • 모든 데이터를 Realm 내부에 캐싱하면 REST API만으로는 불가능한 지역적으로 쿼리하고 검색하며 사용성을 확장할 수 있습니다.
  • Realm에 데이터를 저장하면 새로운 추가되거나 변경된 데이터만을 서버에 요청함으로써 서버 측면의 부담을 줄일 수 있습니다.

적절한 사례

  1. 비동기 요청 - 네트워크 요청과 다른 블록킹 오퍼레이션은 사용자 인터페이스의 블록킹을 막도록 백그라운드 쓰레드에서 동작해야 합니다. Realm에서 많은 수의 객체 입력과 수정은 백그라운드 쓰레드에서 하길 추천합니다. 백그라운드에서 일어나는 변경 사항에 대응하기 위해 알림를 사용할 수 있습니다.
  2. 대용량 데이터 캐싱 - 가능한 자주 사전조회하고 Realm에 로컬 저장하는 방식을 추천합니다. 이러한 방법은 지역적으로 모든 데이터셋에 쿼리를 가능케 합니다.
  3. 삽입 또는 수정 - 데이터셋이 기본키와 같은 고유 식별자를 가지고 있다면 REST API로부터 응답을 받았을 때 +[RLMObject createOrUpdateInRealm:withValue:]를 삽입 혹은 수정 로직을 구현하는데에 손쉽게 사용할 수 있습니다. 이 메소드는 자동적으로 이미 레코드가 존재하는지 검사하고 새 레코드가 생성되는 동안에도 해당 레코드에 변경 사항이 적용됩니다.

예제

아래에는 REST API를 이용하여 Realm을 어떻게 사용할수 있는지에 대한 간단한 예제입니다. 이 예제에서는 foursquare API를 통해서 JSON형식의 데이터를 검색하고, Realm객체를 통해서 기본 Realm에 데이터를 저장합니다.

실시간으로 동작하는 비슷한 예제는 이곳의 비디오 데모를 통해서 확인이 가능합니다.

먼저 API를 통해 데이터셋을 가져오기 위해서 기본 Realm의 인스턴스를 생성합니다. 간단하게 이 예제에서 [NSData initWithContentsOfURL]를 사용했습니다.

// Call the API
NSData *response = [[NSData alloc] initWithContentsOfURL:
                    [NSURL URLWithString:@"https://api.foursquare.com/v2/venues/search?near=San%20Francisco&limit=50"]];

// Deserialize the response to JSON
NSDictionary *json = [[NSJSONSerialization
                       JSONObjectWithData:response
                                  options:kNilOptions
                                    error:&error] objectForKey:@"response"];

이와 같은 JSON 배열이 응답에 포함되어 있습니다:

{
  "venues": [
    {
      "id": "4c82f252d92ea09323185072",
      "name": "Golden Gate Park",
      "contact": {
        "phone": "4152522590"
      },
      "location": {
        "lat": 37.773835608329,
        "lng": -122.41962432861,
        "postalCode": "94103",
        "cc": "US",
        "state": "California",
        "country": "United States"
      }
    }
  ]
}

Realm에는 이러한 JSON을 가져올 수 있는 몇가지 방법이 있습니다. 사용자 지정 삽입을 통해 NSDictionary를 읽고 하나의 RLMObject로 속성을 대응시킬 수 있습니다. 이 예제의 경우 Realm에 직접적으로 NSDictionary를 삽입하는 대신에 RLMObject의 계층구조에 때에 따라 자동적으로 대응되도록 만듭니다. 이 작업을 위해 JSON의 모든 키에 정확히 매칭시킬 RLMObject 구조의 속성이 필요합니다. JSON 키가 RLMObject의 속성에 매칭되지 않을 때에는 삽입되지 않습니다. 아래의 RLMObject 정의는 정상적으로 동작합니다:

// Contact.h
@interface Contact : RLMObject
@property NSString *phone;
@end

@implementation Contact
+ (NSString)primaryKey {
    return @"phone";
}
@end
RLM_ARRAY_TYPE(Contact)

// Location.h
@interface Location : RLMObject
@property double lat; // latitude
@property double lng; // longitude
@property NSString *postalCode;
@property NSString *cc;
@property NSString *state;
@property NSString *country;
@end

@implementation Location
@end
RLM_ARRAY_TYPE(Location)

// Venue.h
@interface Venue : RLMObject
@property NSString *id;
@property NSString *name;
@property Contact  *contact;
@property Location *location;
@end

@implementation Venue
+ (NSString)primaryKey {
    return @"id";
}
@end
RLM_ARRAY_TYPE(Venue)

결과 셋은 배열로 반환되기 때문에 [Venue createInDefaultRealmWithObject:] 호출을 통해 각 엘레멘트를 객체로 만들어야 합니다. 이렇게 JSON으로부터 Venue와 자식 객체가 만들어지고 새롭게 만들어진 객체는 기본 Realm에 추가됩니다.

//Extract the array of venues from the response
NSArray *venues = json[@"venues"];

RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
// Save one Venue object (and dependents) for each element of the array
for (NSDictionary *venue in venues) {
    [Venue createOrUpdateInDefaultRealmWithValue:venue];
}
[realm commitWriteTransaction];

현재 제한 사항

Realm은 현재 베타이며 1.0 공개를 향해 지속적으로 기능을 추가하고 문제점을 보완하고 있습니다. 가장 일반적인 제한 사항을 정리해놨습니다.

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

일반적인 제한

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

  1. 클래스 이름은 0~63 바이트 길이이어야 합니다. UTF8 문자열을 지원합니다. 제한 길이를 넘어서게 되면 앱을 초기화할 때에 예외 처리됩니다.
  2. 속성 이름은 0~63바이트 길이이어야 합니다. UTF8 문자열을 지원합니다. 제한 길이를 넘어서게 되면 앱을 초기화할 때에 예외 처리됩니다.
  3. NSData 속성은 16MB 크기로 제한됩니다. 더 큰 데이터를 저장하기 위해서는 16MB 단위의 작은 파일로 나누거나 파일 시스템에 직접 저장하고 경로를 Realm에 저장하세요. 단일 속성이 16MB를 초과하는 데이터를 저장하려고 시도하면 예외 처리됩니다.
  4. NSDate 속성은 초 단위로만 저장할 수 있습니다. 자세한 정보는 NSDate entry in Current Limitations below에서 확인하세요.
  5. 단일 Realm 파일 크기는 iOS가 대응하는 앱의 메모리 영역 크기를 넘어설 수 없습니다. 이는 디바이스별로 다르고 해당 시점에 얼마나 메모리 공간이 단편화되었는지에 따라 다릅니다 (해당 이슈에 대한 레이더가 있습니다: rdar://17119975). 더 많은 데이터를 저장할 필요성이 있다면 다수의 Realm 파일과 연동할 수 있습니다.

세부적인 알림은 지원하지 않습니다.

알림은 받아볼 수 있지만 (알림) 현재로써는 알림으로부터 무엇이 추가되고/삭제되고/옮겨지고/수정되었는지를 알 수 없습니다. 근시일 내에 이 기능을 추가합니다. Key-Value Observing 는 개체마다 변경 알림으로 사용할 수 있습니다. 특정 개체가 삭제 된 것을 감지 할 수 있습니다.

NSDate는 초 단위로 처리됩니다.

NSDate는 초 단위로 끊어서 처리됩니다. 현재 이 부분을 개선중입니다. 자세한 정보는 GitHub issue #875에 방문해주세요. 현재로는, NSTimeInterval 속성으로 손실없이 저장할 수 있습니다. 그때까지 밀리 초 이하의 정밀도를 가진 시간을 저장할 경우 NSTimeInterval 형의 속성을 사용하십시오.

Realm 객체 Setter 및 Getter는 재정의할 수 없습니다.

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

파일 크기 및 중간 버전 추적

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

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

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

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

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

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

FAQ

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

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

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

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

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

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

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

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

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

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

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