최신 영문 문서를 보려면 이곳을 참고하세요.
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";
mydog.picture = nil; // properties are nullable
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, Swift 2.1을 지원하고 있습니다.
설치
주의 : “Dynamic Framework”는 iOS 7과 호환되지 않습니다. iOS 7 지원을 지원하는 경우 “Static Framework” 을 참고하세요.
- Realm의 최신버전을 다운로드하고, zip 압축을 풉니다.
- Xcode 프로젝트에서 “General” 설정으로 이동합니다.
ios/dynamic/
,watchos
또는osx/
디렉터리에서 “Embedded Binaries” 부분으로Realm.framework
를 옮깁니다. Copy items if needed 를 선택했는지 확인하고 Finish 버튼을 누릅니다. - 유닛 테스트 타깃의 “Build Settings” 탭에서 “Framework Search Paths” 부분에
Realm.framework
의 parent 경로를 추가하세요. 4.Swift와 함께 Realm을 사용한다면, Xcode 프로젝트에서Swift/RLMSupport.swift
파일을 File Navigator에 넣으신 후 Copy items if needed 를 선택합니다. - iOS 또는 watchOS 프로젝트에서 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를 해결해야합니다.
- CocoaPods 0.39.9 버전 또는 그 상위버전을 설치하세요.
- Podfile에 pod ‘Realm’을 앱 타깃에 추가하고 pod ‘Realm/Headers’를 테스트 타깃에 추가하세요.
- 커맨드라인을 통해
pod install
을 실행하세요. - 프로젝트에서 사용하기 위해서는 CocoaPods이 생성한
.xcworkspace
을 사용하세요.
- Carthage 0.9.2 버전 또는 그 상위버전을 설치하세요.
github "realm/realm-cocoa"
를 Cartfile에 추가합니다.carthage update
를 실행합니다.-
iOS : Xcode 프로젝트의 “General” 셋팅에서 “Linked Frameworks and Libraries” 부분에 Carthage/Build/iOS/ 디렉터리로
Realm.framework
를 옮깁니다.OS X: Xcode 프로젝트의 “General” 셋팅에서 “Embedded Binaries” 부분에 Carthage/Build/Mac/ 디렉터리로
Realm.framework
를 옮깁니다.watchOS : Xcode 프로젝트의 “General” 셋팅에서 “Embedded Binaries” Carthage/Build/watchOS/ 디렉터리로
Realm.framework
를 옮깁니다. - iOS: 앱 타깃의 “Build Phases” 셋팅 탭에서 “+” 아이콘을 누르고 “New Run Script Phase” 선택합니다. 다음과 같이 실행 스크립트를 생성합니다:
/usr/local/bin/carthage copy-frameworks
그리고 “Input Files”에 사용할 프레임워크의 경로를 추가합니다. 예를 들면:
$(SRCROOT)/Carthage/Build/iOS/Realm.framework
이 스크립트는 universal binaries 에 의해 App Store submission bug 를 해결해야합니다.
- Realm의 최신 버전을 다운로드하고 압축을 풉니다.
ios/static/
디렉터리에서Realm.framework
을 선택하여 Xcode 프로젝트의 File Navigation에 넣습니다. 이때, Copy items if needed 이 선택된지 확인하고, Finish 버튼을 누릅니다.- Xcode의 File Navigator에서 프로젝트를 클릭합니다. 어플리케이션 대상을 선택하고 Build Phases 탭으로 이동합니다. Link Binary with Libraries 의 +를 클릭하여 libc++.dylib 를 추가합니다.
- Swift와 함께 사용한다면
Swift/RLMSupport.swift
파일을 Xcode의 File Navigator에 넣으신 후 Copy items if needed 를 선택합니다.
tvOS
tvOS는 현재 베타 버전이지만, 우리는 현재 Realm이 tvOS 위에서 어떻게 이용할 수 있는지 확인하고 있습니다. 만약 Realm을 tvOS에서 동작시키는 경우는 어디까지나 개발을 위해서만 이용을 하시고, 프로덕션 용도로는 아직 적합하지 않으니 주의하시길 바랍니다. 자세한 내용은 여기 Pull Request #2506을 참고하세요.
Realm 브라우저
Realm Swift 0.96이나 그 이상 버전에서 사용하는 경우, 현재 Mac App Store 버전에서는 Realm 파일을 열 수 없습니다. GitHub에서 최신 Realm Browser 를 다운로드 하여 이용하시기 바랍니다.
RealmBrowser는 .realm
데이터베이스를 읽고, 편집을 할 수 있는 Mac 응용 프로그램입니다.
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의 Xcode 플러그인을 설치하는 가장 쉬운 방법은 Alcatraz를 이용하여 “RealmPlugin” 을 설치하는 방법입니다. 물론 릴리즈를 통해 제공하는 plugin/RealmPlugin.xcodeproj
을 열어서 직접 설치하여 빌드도 가능합니다. 설치된 플러그인을 확인하기 위해서는 Xcode 재실행이 필요합니다. Xcode 메뉴에서 새 파일을 생성 (File > New > File… — or ⌘N) 할 때, 새 Realm 모델을 생성하는 옵션을 볼 수 있습니다.
API 레퍼런스
Realm에서 사용할 수있는 모든 클래스와 메소드에 대해서는 API Reference 를 참조하십시오.
예제
최신 Realm 의 examples/
에서 Objective‑C, Swift 그리고 RubyMotion 어플리케이션의 예제를 확인 할수 있습니다. 예제에는 마이그레이션, UITableViewController의 사용법, 암호화, 명렁어 툴 및 그 이외의 Realm의 여러 기능 사용법을 포함하고 있습니다.
도움을 얻으려면
- 코드와 관련하여 도움이 필요하시나요? StackOverflow에 물어보세요. 우리는 적극적으로 모니터링과 질문에 대한 답을 해드립니다.
- 버그를 알리고 싶나요? 우리 저장소에 이슈를 등록하세요. 가능하면 Realm의 버전과 전체 로그, 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
.
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 하는지 알려주게 합니다.
관계
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
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
에 언제든지 객체를 추가 할 수 있습니다.
역관계
역관계(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
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
을 사용하여 지정해야 합니다. 할당 된 값은 지정된 형식으로 변환됩니다.
예를 들면, 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
을 지원하고 있지 않습니다.
속성 특성
주의, 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
현재는 문자열과 정수형의 속성만 인덱스가 제공되고 있습니다. 속성에 인덱싱을 하면, =
또는 IN
을 사용한 쿼리의 속도는 크게 향상됩니다. 그 대신에 객체 생성은 조금 느립니다.
기본값
클래스 메소드의 +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에 객체를 저장해야만 합니다.
트랜잭션은 무시할 수 없는 오버헤드가 발생하기 때문에 간으한 트랜잭션의 수는 최소화 하는 것이 바람직합니다.
트랜잭션은 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]];
- 가장 명백한 건 (Objective‑C에서) alloc-init을 사용하거나 (Swift에서) 지정된 초기화를 사용하는 것입니다. 모든 속성들은 객체가 Realm에 추가되기 전에 설정해야한다는 것을 유의하세요.
- 객체는 고유한 키와 값을 사용하여 dictionary에 만들 수 있습니다.
- 마지막으로 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 설계 덕분에 쓰기 트랜잭션이 실행 중에는 읽기는 제한되지 않습니다. 한 번에 여러 쓰레드에서 동시 쓰기를 해야하지 않는 이상, 대량의 쓰기 트렌잭션으로 구성하기 보다는 다수의 세세한 쓰기 트랜잭션으로 구성하길 선호하실 겁니다.
자세한 내용은 RLMRealm과 RLMObject를 확인하세요.
객체 업데이트
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를 반환합니다.
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 *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"]
. -
Nil 비교 ==, !=
예.
[Company objectsWhere:@"ceo == nil"]
.nil
자체가 같은 SQL과 다르게 값이 없는 것보다nil
처럼 특별한 값으로 지정합니다. -
객체를 대상으로한 관계에서만 유효합니다. 이 예에서는 ceo은 Company에서 속성으로 정의되어 있다.
-
ANY 비교.
예. ANY student.age < 21
-
집계 표현 타입
@count
,@min
,@max
,@sum
와@avg
등을 RLMArray에서 지원을 합니다.예.
[Company objectsWhere:@"employees.@count > 5"]
라 쓰면, 5명 이상의 직원을 가진 모든 회사를 찾을 수 있습니다.
좀 더 상세한 내용은 [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'"];
자동 업데이트
RLMResults는 실시간으로 기본 데이터가 자동으로 뷰에 업데이트된다. 이는 결과가 다시 패치가 될 필요가 없다는 의미입니다. 쿼리에 영향을 주는 수정되는 객체는 즉시 결과가 반영이 됩니다.
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 데이터가 업데이트 될때 RLMResults를 다시 가지고 올 필요 없이 UI가 갱신된다는 것을 알기 위해서 Realm notifications을 구독합니다.
경과가 자동으로 업데이트 되기 때문에 일정한 인덱스와 개수를 유지하는 것이 중요합니다. RLMResults이 고정되어 있는 유일한 시간은 그 위에 빠르게 열거할때, 열거하면서 쿼리에 일치하는 객체가 변형된 경우입니다.
[realm beginWriteTransaction];
for (Person *person in [Person objectsInRealm:realm where:@"age == 10"]) {
person.age++;
}
[realm commitWriteTransaction];
또한, RLMResults에서 작업을 수행하기 위해 key-value coding 사용합니다.
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에 메모리 참조를 하도록 추천합니다.
오류 헨들링
일반적인 disk IO 처리와 마찬가지로, RLMRealm 인스턴스 생성은 자원이 부족한 환경에서는 실패 할 수 있습니다. 실제로 각 스레드에서 처음 Realm 인스턴스를 만들려고 할 때 오류가 발생할 수 있습니다. 그 이후 접근에서는 스레드 마다 캐시된 인스턴스가 반환되기 때문에 실패하는 것은 아닙니다.
오류를 처리하고 복구하려면 NSError
포인터를 error
매개 변수로 전달합니다.
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
if (!error) {
// handle error
}
Realm 간의 객체 복사
Realm에 저장된 객체를 다른 Realm에 복사하려면 +[RLMObject createInRealm:withValue:]
메소드에 원본 객체를 인수로 전달합니다. 예를 들면. [MyRLMObjectSubclass createInRealm:otherRealm withValue:originalObjectInstance]
와 같이 사용을 하면됩니다.
Realm 객체는 처음 만들어진 스레드에서 접근할 수 있기 때문에 이 복사본은 동일한 스레드에서 Realm이 작동된다는 것을 기억하세요.
Realm File 찾는 방법
여러분의 앱의 Realm 파일의 위치를 모르는 경우에는 StackOverflow 답변을 참고하세요.
Realm을 앱에 번들링
처음 부팅을 빠르게 하기 위해서 같은 앱에 초기 데이터를 통합하는 것이 많습니다. 다음은 Realm을 초기 데이터로 번들하는 예입니다.
-
먼저 초기 데이터가 들어있는 Realm을 제공합니다. 출시 때와 같은 데이터 모델을 정의하고 데이터를 넣습니다. Realm 파일은 크로스 플랫폼에서 사용할 수 있기 때문에 데이터의 작성은 OS X와 iOS 시뮬레이터에서 해도 문제 없습니다. (JSONImport example를 참고하세요.)
-
초기 데이터가 포함됨 코드의 끝부분에 Realm 파일을 복사하는 방법을 사용하고, Realm 파일 크기를 최적화하세요. (
-[RLMRealm writeCopyToPath:error:]
를 참고) 이 메소드를 사용하여 Realm 파일을 복사하면 Realm 파일 크기를 줄일 수 있고 결국엔 앱의 크기가 가벼워지기 때문에 사용자가 더 빠르게 다운로드 할 수 있습니다. - 복사된 Realm 파일을 Xcode 프로젝트 내비게이터에 드래그 앤 드롭을 합니다.
- 프로젝트 설정 Build Phase 탭에서 “Copy Bundle Resources”에 Realm 파일을 추가합니다.
- 이 시점에서 추가한 Realm 파일에 앱에서 접근할 수 있습니다.
[[NSBundle mainBundle] pathForResource:ofType:]
메소드를 사용하여 파일 경로를 가지고 옵니다. - 동봉한 Realm 데이터 파일이 고정 데이터만을 포함하여 변경할 필요가 없는 것이면, RLMRealmConfiguration에서
readOnly = true
를 지정하여 읽기 전용으로 직접 번들 내의 파일을 열 수 있습니다. 반면에 초기 데이터를 변경할 필요가 있다면, 번들에서 문서 디렉터리 등에 Realm 파일을[[NSFileManager defaultManager] copyItemAtPath:toPath:error:]
메소드로 복사하고 사용할 수 있습니다.
Realm 파일을 초기 데이터로 통합한 예로 migration sample app를 참고하세요.
Class Subsets
각 Realm 파일에 저장되는 모델의 정의를 구분하고 싶은 것이 있습니다.
에를 들어서 내부에 Realm을 사용하는 다른 구성 요소를 두 팀이 개발하고 있다면, 그들 간에 마이그레이션을 조정하고 싶지 않을 것입니다. 아래와 같이, RLMRealmConfiguration의 objectClasses
을 이용해 각각의 Realm에 저장하는 모델 클래스를 제한할 수 있습니다:
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.objectClasses = @[MyClass.class, MyOtherClass.class];
RLMRealm *realm = [RLMRealm realmWithConfiguration:config];
스레드
별도의 스레드에서 Realm을 사용하고 있으면, Realm 모든 객체는 일반적인 객체처럼 동시성과 멀티 스레딩에 대한 걱정없이 처리할 수 있습니다. Realm(다른 스레드에 동시에 업데이트 된다고 해도)에 접근하기 위해 잠금이나 단독적으로 처리를 생각할 필요가 없습니다.
Realm은 동시 처리를 쉽게 처리 할 수 있도록 하기 위해서 각 스레드에서 항상 일관성있게 데이터를 반환합니다. 많은 스레드에서 Realm을 동시에 조작한다면, 각각의 스레드마다 스냅 샷 데이터를 반환하고 일관성이 없는 상태가 될 수 있습니다.
한가지 주의해야 할 것은 여러 스레드를 걸처 같은 Realm 인스턴스를 공유할 수 없다는 것입니다. 만약에 여러 스레드에서 동일한 객체에 접근할 필요가 있는 경우에는 각각의 스레드가 Realm 인스턴스를 지정해야합니다. (그렇지 않으면 데이터가 일관성적이지 않게 보입니다.)
다른 스레드에서 변경된 데이터를 반영하기
메인 UI 스레드(또는 Runloop의 스레드) 객체는 런루프가 돌 때마다 자동으로 다른 스레드에서 변경된 데이터가 반영됩니다. 아무때나 그 시점의 스냅 샷 데이터를 반환하고 다른 스레드에서 데이터가 변경되었는지를 걱정할 필요없이 항상 일관된 데이터를 볼 수 있습니다.
먼저 Realm 파일을 열 때, Realm의 상태는 마지막 커밋이 성공한 상태입니다. 그리고 다음 업데이트가 반영 될 때 까지 그대로입니다. Realm은 autorefresh
이 NO
인 경구가 아니라면 런루프가 돌아올때마다 자동으로 최신 데이터로 업데이트됩니다. 만약에 스레드가 런루프를 가지고 있지 않은 일반적인 백그라운드 스레드인 경우에는 최신의 데이터를 반영하기 위해서 [-[RLMRealm refresh]
메소드를 직접 호출해야 합니다.
또한 트랜잭션이 커밋된 경우에도 최신 데이터가 반영이 됩니다. ([-[RLMRealm commitWriteTransaction]
).
주기적으로 최신 데이터 반영이 실패면 트랜잭션이 “pinned”되어 디스크 공간의 재사용을 방해합니다. 그것은 파일 크기가 커지는 것을 초래할 수 있습니다. 이 현상에 대한 자세한 내용은 현재 버전의 제한 사항을 참조하세요.
스레드 간의 객체 전달
독립형 (영속되지 않은) RLMObject의 인스턴스는 일반적으로 NSObject의 인스턴스가 됩니다. 스레드 전반에 객체를 전달해도 문제 없습니다.
RLMRealm, RLMObject, RLMResults, 또는 RLMArray의 지속된 인스턴스는 생성된 스레드 내에 있어야 사용할 수 있습니다. 다른 스레드에서 사용되면 예외가 발생합니다. 이것은 Realm 트랜잭션을 분리하기 위해 필요한 것입니다. 그렇지 않으면 객체가 잠재적으로 광법위한 관계 그래프와 함께 다른 트랜잭션에서 스레드 사이를 통과하는 것을 결정하는 것은 불가능하다.
스레드 사이를 가로 질러 객체를 전달하는 방법을 소개합니다. 예를 들어 primary key인 객체는 기본키 값으로 표현할 수 있으며, RLMResults는 NSPredicate
또는 쿼리 문자열로 표현할수 있다. 또, RLMRealm는 RLMRealmConfiguration로 표현할수 있다. 타깃 스레드는 스레드 세이프을 사용하여 { RLMRealm %>, RLMObject, RLMResults, RLMArray을 다시 가지고 올 수 있습니다. 다시 패치하는 것은 원래 스레드와 다를 수 있습니다. 대상 스레드의 버전에서 객체를 검색하니 유의하세요.
- RLMRealm: 모든 속성, 클래스 메소드, initializers
- RLMObject:
invalidated
,objectSchema
,realm
, class methods, 과 initializers. - RLMResults:
objectClassName
과realm
. - RLMArray:
invalidated
,objectClassName
, 과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 객체는 쓰기 트랜잭션이 커밋될 때마다 여러 쓰레드의 다른 객체에 알림을 보냅니다. 이러한 알림은 블록을 등록하여 구독할 수 있습니다:
// Observe Realm Notifications
self.token = [realm addNotificationBlock:^(NSString *note, RLMRealm * realm) {
[myViewController updateUI];
}];
반환되는 알림 토큰에 레퍼런스를 참조하는 동안 만큼 알림은 구독됩니다. 토큰이 메모리 해제되었을 때 자동적으로 알림이 구독 취소되므로 업데이트를 등록한 클래스 내의 토큰에 참조를 걸어두세요.
보다 자세한 내용은 [RLMRealm addNotificationBlock:]
과 [RLMRealm removeNotificationBlock:]
을 확인하세요.
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) 의 샘플이 있으므로 참고하세요.
마이그레이션
다른 어떤 데이터베이스로 작업할 때와 마찬가지로 데이터 모델은 시간이 지남에 따라 변경됩니다. 표준 클레스로 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
이 시점에 어떠한 데이터라도 저장했다면 코드에 정의된 모델과 디스크에 기록된 모델 사이에 불일치가 발생합니다. 이러한 상황이 발생할 때 기존 파일을 열려고 하면 마이그레이션이 진행되지 않고 예외 처리됩니다.
주의해야하는 것은 기본 프로퍼티 값은 마이그래이션 중의 새 객체에 적용이 되지 않습니다. 이 문제는 #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 및 모든 개체를 평소와 같이 접근 할 수 있습니다.
버전 추가
두 버전의 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을 열기 전에 등록되어야 하고 그렇지 않으면 잘못된 경고 알림을 받게 됩니다.
디버깅
LLDB 지원과 실시간으로 Realm Browser을 통해 앱 내부 데이터를 볼 수 있어서 손쉽게 Realm 앱을 디버깅할 수 있습니다.
속성 값이 nil
혹은 0
으로만 표시되는 대신에 우리의 Xcode 플러그인에 Xcode UI에서 RLMObject, RLMResults, RLMArray 객체 디버깅을 지원하는 LLDB 스크립트가 포함되어 있습니다.
알림: 해당 스크립트는 현재 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에 데이터를 저장하면 새로운 추가되거나 변경된 데이터만을 서버에 요청함으로써 서버 측면의 부담을 줄일 수 있습니다.
적절한 사례
- 비동기 요청 - 네트워크 요청과 다른 블록킹 오퍼레이션은 사용자 인터페이스의 블록킹을 막도록 백그라운드 쓰레드에서 동작해야 합니다. Realm에서 많은 수의 객체 입력과 수정은 백그라운드 쓰레드에서 하길 추천합니다. 백그라운드에서 일어나는 변경 사항에 대응하기 위해 알림를 사용할 수 있습니다.
- 대용량 데이터 캐싱 - 가능한 자주 사전조회하고 Realm에 로컬 저장하는 방식을 추천합니다. 이러한 방법은 지역적으로 모든 데이터셋에 쿼리를 가능케 합니다.
- 삽입 또는 수정 - 데이터셋이 기본키와 같은 고유 식별자를 가지고 있다면 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에서 정보를 저장하는 다양한 측면에서 존재합니다. 예를 들어:
- 클래스 이름은 0~63 바이트 길이이어야 합니다. UTF8 문자열을 지원합니다. 제한 길이를 넘어서게 되면 앱을 초기화할 때에 예외 처리됩니다.
- 속성 이름은 0~63바이트 길이이어야 합니다. UTF8 문자열을 지원합니다. 제한 길이를 넘어서게 되면 앱을 초기화할 때에 예외 처리됩니다.
- NSData 속성은 16MB 크기로 제한됩니다. 더 큰 데이터를 저장하기 위해서는 16MB 단위의 작은 파일로 나누거나 파일 시스템에 직접 저장하고 경로를 Realm에 저장하세요. 단일 속성이 16MB를 초과하는 데이터를 저장하려고 시도하면 예외 처리됩니다.
- NSDate 속성은 초 단위로만 저장할 수 있습니다. 자세한 정보는 NSDate entry in Current Limitations below에서 확인하세요.
- 단일 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)의 바이너리 라이센스 내에서 사용할 수 있습니다.