최신 영문 문서를 보려면 이곳을 참고하세요.
시작하기
수동 설치
- 최신버전의 Realm 을 다운로드 하고 압축을 풉니다.
ios/
또는osx/
디렉터리(대상 플랫폼에 따라 선택)에서 Realm.framework을 선택하여 Xcode 프로젝트의 Frameworks폴더에 넣습니다. 이때, 선택된 대상 그룹의 폴더에 파일이 복사 되는 것을 꼭 확인 하시고, Finish버튼을 누릅니다.- Xcode 파일 익스플로어에서 프로젝트를 클릭하십시요. 대상을 선택하고 Build Phases탭으로 이동합니다. Link Binary with Libraries의 +를 클릭하여 libc++.dylib를 추가합니다.
CocoaPods를 통한 설치
만약 CocoaPods을 사용하신다면…
- Podfile에 다음 문장을 추가합니다.:
pod "Realm"
. - 커맨드라인 프롬프트에서,
pod install
를 실행합니다. - 프로젝트에 CocoaPods을 통햇 생성된
.xcworkspace
파일을 사용합니다.
- github.com/realm/realm-cocoa에서 소스코드를 다운로드 합니다.
- Xcode 프로젝트에
Realm-Xcode6.xcodeproj
를 추가합니다. 꼭 Xcode6-Beta5임을 확인하십시요. 이외의 Xcode 6는 아직 미지원중입니다. (Beta6지원을 곧 될 예정입니다. PR #814) - 앱의 “Build Phases”탭을 선택하여 “Target Dependencies”항목을 펼쳐서 (2)의 +마크를 클릭합니다.
(이미지를 클릭하면 큰 이미지를 볼 수 있습니다.)
- 리스트에서 “iOS”를 선택합니다.
- “Link Binary with Libraries”항목을 펼치고 +마크를 클릭하여 Realm.framework (iOS)를 선택합니다.
- 왼쪽 상단의 +마크를 클릭하고 “New Copy Files Phase”를 선택합니다.
- 추가된 “Copy Files Phase”에서, destination을 “Resources”에서 “Frameworks”으로 변경합니다.
- 추가된 “Copy Files Phase”에서 +마크를 클릭하여 Realm.framework를 선택합니다.
- Xcode는 OSX 와 iOS 버전의 Realm을 자동적으로 추가해 줄 것입니다. “iphoneos”이 포함되지 않은 프레임워크는 삭제합니다.
- “Defines Module”은 YES로 변경합니다.
- 빌드 한 후 실행
Xcode 플러그인
제공되는 Xcode 플러그인은 새로운 Realm 모델들을 쉽게 만들 수 있도록 도와줍니다.
Realm의 Xcode 플러그인을 설치하는 가장 쉬운 방법은 Alcatraz 를 이용하여 “RealmPlugin” 설치하는 것입니다. 물론 release zip을 통해 제공하는 plugin/RealmPlugin.xcodeproj
을 열어서 직접 설치하여 빌드 하는 것 또한 가능합니다. 설치된 플러그인을 확인하기 위해서는 Xcode의 재실행이 필요합니다. Xcode 메뉴에서 새 파일을 생성 (File > New > File… — or ⌘N) 할 때, 새 Realm 모델을 생성하는 옵션을 볼수 있을 것 입니다.
Realm 브라우저
또한 realm 데이터베이스를 읽고 수정할수 있는 앱을 제공하고 있습니다. release zip의 browser/
아래에서 확인 할 수 있습니다.
Tools > Generate demo database메뉴를 통해서는 테스트용 데이터베이스와 더미 데이터를 생성할수 있습니다.
만약 Xcode 6 베타를 설치하였거나 요세미티를 사용중이라면 애플의 베타 릴리즈때 버그로 인한 “unidentified developer” 에러를 접할 수 있습니다. 이런 경우에는 앱에 오른쪽 클릭으로 “열기”를 선택합니다. 여전히 경고 메세지는 표시되지만, “open the app anyway”버튼이 보일 것 입니다. 이후에는 더블클릭을 통한 실행에도 경고 메세지가 표시 되지 않을 것 입니다. (팁을 제공해준 @apalancat에게 감사드립니다!)
예제
release zip의 ios/examples/
에서 Object-C 애플리케이션의 예제를 확인 할수 있습니다. 예제에는 마이그레이션, UITableViewController의 사용법, 암호화 및 그 이외의 많은 Realm의 여러 기능의 사용법을 포함하고 있습니다.
도움을 얻으려면
- community newsletter에 가입하여 일반적인 팁을 얻거나, 여러 다른 사례들을 배우고 Realm에 대한 블로그 포스팅이나 튜토리얼등을 확인 할수 있습니다.
- StackOverflow: #realm 태그가 포함된 이전 질문등을 찾거나 새로운 질문을 등록할 수 있습니다.
- Twitter: 공식 계정 @realm 에 연락 하거나 또는 #realm 태그를 이용할 수 있습니다.
- Email: realm-cocoa@googlegroups.com.
모델(Models)
Realm 데이터 모델은 @properties에 일반적인 NSObject 스타일의 클래스를 사용하여 정의할 수가 있습니다. 간단하게 서브클래스 RLMObject를 Realm데이터 모델 객체로 만들수 있습니다. Realm 모델 객체는 여타의 Objective‑C의 객체들과 기능상으로 거의 같습니다
- 자신만의 메소드와 프로토콜들을 추가할수 있고 다른 객체와 동일하게 사용할 수 있습니다. 단 제한이 있다면, 생성한 쓰레드 안에서만 사용할수 있다는 점과 ivars에 직접적으로 다른 관련된 속성들에 접근할수 없다는 점 입니다.
만약 이미 제공중인 Xcode Plugin을 설치하였다면 “New File…” 다이얼로그를 통해서 인터페이스와 동작하는 파일들의 좋은 템플릿을 확인 할 수 있습니다.
관계(relationship)와 자료구조는 타깃 타입의 속성이나 RLMArray의 객체 리스트들을 포함하여 간단하게 정의 할 수 있습니다.
@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> *dogs;
@end
RLM_ARRAY_TYPE(Person) // define RLMArray<Person>
// Implementations
@implementation Dog
@end // none needed
@implementation Person
@end // none needed
// Dog model
class Dog: RLMObject {
dynamic var name = ""
dynamic var owner = Person()
}
// Person model
class Person: RLMObject {
dynamic var name = ""
dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
dynamic var dogs = RLMArray(objectClassName: Dog.className())
}
더 자세한 내용은 RLMObject에서 확인 할 수 있습니다.
속성 타입(Property Types)
Realm이 지원중인 속성 타입은 다음과 같습니다: BOOL
, bool
, int
, NSInteger
, long
, float
, double
, CGFloat
, NSString
, NSDate
, NSData
. 또한, RLMObject
와 RLMArray<x>
의 서브클래스 역시 모델의 관계(relationship)로 지원 중입니다.
속성 특성(Property Attributes)
주의, Realm은 Objective‑C의 속성 특성중에 nonatomic
, atomic
, strong
, copy
, weak
, 기타등등을 무시합니다. Realm은 내부적으로 이미 최적화된 스토리지 semantics을 가지고 있기 때문입니다. 그래서 오해의 소지를 피하기 위해서, 이러한 속성의 특성을 모델에 사용하지 않을 것을 권고 합니다. 그러나 만약 이러한 속성 특성들을 설정하였다면 한 영역에 RLMObject을 추가하기 전까지는 사용할 수 있을 것 입니다. 사용자 지정의 getters와 setters는 문제 없이 사용 할 수 있지만, 한 영역의 RLMObject에서는 사용이 불가능 할 것 입니다.
쓰기
모든 객체의 변경(추가, 수정, 삭제)는 write트랜젝션을 통해서 처리 됩니다.
Realm객체는 보통의 객체들과 같이 단독적으로 값을 지정하고 사용이 가능합니다. 쓰레드 사이에 객체를 공유하거나 실행중인 앱 간에 재사용을 하기 위해서는 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];
// Create a Person object
let author = Person()
author.name = "David Foster Wallace"
// Get the default Realm
let realm = RLMRealm.defaultRealm()
// Add to the Realm inside a transaction
realm.beginWriteTransaction()
realm.addObject(author)
realm.commitWriteTransaction()
Realm에서 객체를 생성한 후에는 그 객체는 계속적으로 사용할 수 있습니다. 그리고 그것을 구성하는 모든 변경사항은 같은 영역을 사용하여 다른 쓰레드에서도 역시 지속적으로 사용가능 할 것 입니다.
이 점은 주의 하시기 바랍니다. 동시에 각각의 블럭에 서로 다른 쓰기가 진행중이라면 해당 쓰레드는 블럭이 될 것입니다. 이 것은 여러 다른 범용 솔루션들과 비슷합니다. 그래서 이런 경우에는 보통의 모범사례를 따를것을 권고합니다. 즉, 쓰기에 대해서 쓰레드를 나누는 것을 말합니다.
또한, Realm의 MVCC아키텍쳐에 감사할 것은 쓰기 트랜젝션이 실행중에는 읽기는 제한되지 않습니다! 이 뜻은 한 번에 여러 쓰레드에서 동시 쓰기를 해야 하지 않는 이상, 대량의 쓰기 트랜젝션으로 구성하기 보다는 다수의 세세한 쓰기 트랜젝션으로 구성하는 것이 효율적 이라는 뜻이기도 합니다.
자세한 내용은 RLMRealm과 RLMObject를 확인해 주십시요.
쿼리
Realm에서 쿼리를 포함한 모든 페치(fetch)는 느립니다. 또한, 데이터들은 복사되지 않습니다.
Realm배열(RLMArray)을 사용시의 주의점: 모든 검색 및 조회 메소드의 호출성공 결과는 RLMArray의 RLMObjects컬렉션을 반환합니다. RLMArray는 표준 NSArray와 비슷하게 조작 할수 있습니다; 그러나, RLMArray는 같은 RLMObject의 서브클래스 타입의 RLMObject만을 가지고 있도록 되어 있습니다. 자세한 내용은 RLMArray를 참조하여 주십시요.
타입을 통한 객체 검색(Retrieving Objects by Type)
Realm에서 가장 기본적인 객체 검색 메소드는 [RLMObject allObjects]
인데, 이는 기본 Realm 으로부터 같은 서브 클래스 타입의 모든 RLMObject 인스턴스들을 반환할 것 입니다.
// On the default Realm:
RLMArray *dogs = [Dog allObjects]; // retrieves all Dogs from the default Realm
// On a specific Realm
RLMRealm *petsRealm = [RLMRealm realmWithPath:@"pets.realm"]; // get a specific Realm
RLMArray *otherDogs = [Dog allObjectsInRealm:petsRealm]; // retrieve all Dogs from that Realm
// Query the default Realm
let dogs = Dog.allObjects()
// Query a specific Realm
let petsRealm = RLMRealm.realmWithPath("pets.realm")
let otherDogs = Dog.allObjectsInRealm(petsRealm)
조건 쿼리(Querying with Predicates)
만약에 이미 NSPredicate에 익숙하다면, Realm에서의 조회(query)는 어렵지 않을 것입니다. RLMObjects, RLMRealm, RLMArray의 모든 제공되는 메소드들은 NSObject인스턴스와 마찬가지로 조회하길 원하는 특정한 RLMObject인스턴스로 NSPredicate인스턴스, 조건 문자열 또는 조건 형식의 문자열 을 단순하게 전달할 수 있을 것 입니다.
예를 들면, 아래는 이전의 예시로부터 확장하여 [RLMObject objectsWhere:]
를 호출하여, 기본 Realm으로 부터 색이 ‘tan’이고 이름이 ‘B’로 시작하는 모든 개를 검색할 수 있습니다:
// Using a predicate string
RLMArray *tanDogs = [Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"];
// … Or using an NSPredicate object
NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@",
@"tan", @"B"];
RLMArray *tanDogs2 = [Dog objectsWithPredicate:pred];
// Query using a predicate string:
let tanDogs = Dog.objectsWhere("color = 'tan' AND name BEGINSWITH 'B'")
// Query using an NSPredicate object:
let predicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "tan", "B")
let tanDogs2 = Dog.objectsWithPredicate(predicate)
애플의 Predicates Programming Guide에서 조건절에 대한 상세한 정보를 얻을 수 있습니다. 아래는 조건절 구현에 대한 상세내용입니다:
- 비교 연산자는 속성 이름 또는 피연산자가 될수 있습니다. 피연산자중 적어도 하나는 속성의 이름이어야 합니다.
- 비교 연산자 ==, <=, <, >=, >, !=, BETWEEN는 int, long, float, double, NSDate 속성타입을 지원합니다.
- 비교 연산자 ==, !=는 bool 속성을 지원합니다.
- NSString, NSData속성을 위해서 ==, !=, BEGINSWITH, CONTAINS, ENDSWITH 연산자를 지원합니다.
- Realm은 다음의 연산자 또한 지원합니다: “AND”, “OR”, “NOT”.
- 주의, Realm이 집합 표현 타입을 지원하지 않더라도, 객체 값에 사용하기 위하여 BETWEEN 연산자를 지원하고 있습니다. 예:
RLMArray *results = [Person objectsWhere:@"age BETWEEN %@", @[42, 43]];
좀 더 상세한 내용은 [RLMObject objectsWhere:]
을 통해 확인 할 수 있습니다.
결과값 정렬(Ordering Results)
많은 케이스에서 정렬된 결과값을 조회 및 검색 하는것이 편리하다는 알 수 있습니다. 이 기능을 사용 하면, RLMArray는 결과값을 특정한 속성 이름을 지정하여 정렬시킬수가 있습니다.
예를 들어, 아래는 [RLMObject objectsWhere:where:]
의 결과값을 이름 알파벳순으로 정렬하는 코드입니다.
// Using a string (sort is ascending by default)
RLMArray *sortedDogs = [[Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"]
arraySortedByProperty:@"name" ascending:YES];
// Using a string (sort is ascending by default)
var sortedDogs = Dog.objectsWhere("color = 'tan' AND name BEGINSWITH 'B'").arraySortedByProperty("name", ascending: true)
[RLMObject objectsWhere:]
과 [RLMArray arraySortedByProperty:ascending:]
을 통해 상세 내용을 확인 할 수 있습니다.
연속 조회(Chaining Queries)
Realm의 당신의 애플리케이션 데이터를 일반 객체를 사용하여 저장 하는 Realm의 또 다른 장점은 각각의 연속적인 쿼리에 대한 데이터베이스 서버에 별도의 연결을 요구하는 기존의 데이터베이스와 비교하여, 매우 적은 오버헤드로 연속된 조회를 할 수 있다는 점 입니다.
예를 들자면, 만약에 ‘tan’색의 개를 조회한후, ‘tan’색의 개들중에서 이름이 ‘B’로 시작하는 개를 찾기 원할때 아래와 같이 연결된 쿼리로 조회가 가능합니다:
RLMArray *tanDogs = [Dog objectsWhere:@"color = 'tan'"];
RLMArray *tanDogsWithBNames = [tanDogs objectsWhere:@"name BEGINSWITH 'B'"];
let tanDogs = Dog.objectsWhere("color = 'tan'")
let tanDogsWithBNames = tanDogs.objectsWhere("name BEGINSWITH 'B'")
Realms
기본 Realm(The Default Realm)
우리가 항상 [RLMRealm defaultRealm]
를 호출하여 우리의 realm
변수를 초기화 하여 접근한 것을 알 수 있을 것 입니다. 그 메소드는 RLMRealm 객체에 당신의 앱상에 문서 폴더안의 “default.realm”파일을 맵핑 하여 반환 하는 것입니다. 쓰기 트랜젝션은 해당위치의 디스크에 자동적으로 쓰기를 할 것이며, 읽기 역시 동일합니다.
다른 Realm(Other Realms)
때때로 여러개의 realm들을 가지고 있는것은 유용할 수도 있습니다. 예를 들어, 기능별로 다른 데이터베이스를 가지고 있다거나 또는 앱에서 읽기 전용의 파일들을 모아놓은 필요가 있어, 사용자들이 편집 가능한 데이터베이스와 분리하여야 할 경우가 있습니다.
더 자세한 내용은 [RLMRealm realmWithPath:]
과 [RLMRealm realmWithPath:readOnly:error:]
에서 확인 할 수 있습니다.
한개의 Realm 을 쓰레드간에 공유하여 사용
만약에 다른 쓰레드들로 부터 같은 realm파일에 접근하기를 원한다면, [RLMRealm defaultRealm]
, [RLMRealm realmWithPath:]
또는 [RLMRealm realmWithPath:readOnly:error:]
로 앱의 각각의 쓰레드는 다른 Realm객체로 접근 하여야만 합니다. 같은 경로을 지정할 경우에는, 모든 RLMRealm객체는 디스크상의 같은 파일을 매핑할 것 입니다. RLMRealm 객체는 쓰레드간에 공유하지 마십시요.
In-Memory 기본 Realm
기본 Realm은 기본적으로 디스크에 유지됩니다. 그러나 아래와 같이 [RLMRealm defaultRealm]
를 호출하기 전까지는 순수하게 메모리에서 사용 할 수 있습니다.
[RLMRealm useInMemoryDefaultRealm];
RLMRealm *realm = [RLMRealm defaultRealm]; // Only call this line after!
// You must call this method before accessing the default Realm
RLMRealm.useInMemoryDefaultRealm()
let realm = RLMRealm.defaultRealm()
이 옵션은 오직 기본 Realm에 대해서만 사용이 가능합니다.
만약 기본Realm을 메모리상에서 사용하였을 경우, RLMObjects에는 유지되지 않습니다. 즉, 앱에는 저장되지 않는다는 뜻 입니다.그러나, 조회, 관계, 쓰레드 보호를 포함한 Realm의 모든 기능은 동작할 것입니다. 이것은 디스크 오버헤드 없이 유연하게 데이터에 접근이 필요할때는 유용할 것입니다.
주의: in-memory Realm의 대한 참조가 범위를 벗어나게 되면 모든 데이터는 해제됩니다. 우리는 이 기능을 보다 직관적으로 만들기 위해 API및 semantics를 개선하는 것을 고려하고 있습니다.
관계(Relationships)
어떠한 2개의 RLMObject라도 서로 연결될 수 있습니다. Person모델은 이미 정의되어 있다고 가정합니다. (see above) Dog이라고 하는 다른 모델을 만들어 봅시다:
// Dog.h
@interface Dog : RLMObject
@property NSString *name;
@end
class Dog: RLMObject {
dynamic var name = ""
}
다 대 일(Many-to-One)
간단하게 주어진 RLMObject의 서브클래스를중 한 타입을 속성으로 정의하여 봅니다:
// Dog.h
@interface Dog : RLMObject
... // other property declarations
@property Person *owner;
@end
class Dog: RLMObject {
... // other property declarations
dynamic var owner = Person()
}
이것은 owner라는 이름을 가진 Person타입의 속성을 만들것 입니다. 그러면 당신은 다른 속성들과 같이 할당하고 읽을수 있을 것 입니다.
Person *jim = [[Person alloc] init];
Dog *rex = [[Dog alloc] init];
rex.owner = jim;
let jim = Person()
let rex = Dog()
rex.owner = jim
여러분이 쿼리를 수행할때 자식은 쿼리시에 메모리로 가져올수 없습니다. 그러나, 당신은 자식을 수동으로 가져오지 않고도 자동적으로 링크를 찾을수 있을것 입니다. i.e. 다음의 rex.owner.address.country
과 같이 호출하게 되면 자동적으로 객체그래프를 통과하여 필요로 하는 객체들을 반환할 것 입니다.
다 대 다(Many-to-Many)
여러분은 RLMArray<Object>의 속성 정의을 통해서 한개의 객체에 다수의 객체와 관계를 설정 할 수 있습니다. RLMArray는 기본적으로 RLMObject의 컨테이너 입니다. 그것들이 타입이라는 것을 제외하고는 NSArray와 매우 유사합니다. “dogs”속성을 여기 Person모델에 추가하여 복수의 dogs와 연결하려면 , RLMArray<Dog>
타입을 먼저 정의해야 합니다. 이것은 모델 인터페이스에 해당하는 아래의 매크로를 통해 완료 됩니다.
//Dog.h
@interface Dog : RLMObject
... // property declarations
@end
RLM_ARRAY_TYPE(Dog) // Defines an RLMArray<Dog> type
// Not needed in Swift
그러면 여러분은 RLMArray<Dog>
의 속성을 정의할 수 있게 됩니다:
// Person.h
@interface Person : RLMObject
... // other property declarations
@property RLMArray<Dog> *dogs;
@end
class Person: RLMObject {
... // other property declarations
dynamic var dogs = RLMArray(objectClassName: Dog.className())
}
그러면 여러분은 RLMArray속성에 이와 같이 접근하고 할당 할수 있게 됩니다:
// Jim is owner of Rex and all dogs named "Fido"
RLMArray *someDogs = [Dog objectsWhere:@"name contains 'Fido'"];
[jim.dogs addObjectsFromArray:someDogs];
[jim.dogs addObject:rex];
let someDogs = Dog.objectsWhere("name contains 'Fido'")
jim.dogs.addObjectsFromArray(someDogs)
jim.dogs.addObject(rex)
주의: 모델에서 RLMArray 속성은 “쓰기 복제”입니다. 해당 속성의 모든 직접 할당은 할당자에게서 개체에 대한 참조를 복사하게 됩니다. 예를 들면, 그 뜻은 어떤 dogs에 some_dogs
를 추가한 후 jim.dogs = some_dogs;
는 jim.dogs
에 추가되지 _않을것_이라는 뜻 입니다.
알림(Notifications)
자동으로 업데이트하는 Realm은 기본 영역이 업데이트 될 때마다 알림을 보내드립니다. 이러한 알림은 블록을 등록하여 감시 할 수 있습니다:
// Observe Realm Notifications
self.token = [realm addNotificationBlock:^(NSString *note, RLMRealm * realm) {
[myViewController updateUI];
}];
let token = realm.addNotificationBlock { note, realm in
viewController.updateUI()
}
여러분은 나중에 알림 구독을 취소하는데에 토큰을 사용할 수 있습니다. 알림으로부터 여러분이 자동으로 해제하길 원할때 참조하기 위하여 이 토큰을 메인클래스에 저장해 두는것을 추천합니다.
보다 자세한 내용은 [Realm addNotificationBlock:]
과 [Realm removeNotificationBlock:]
을 확인해 주십시요.
백그라운드 오퍼레이션(Background Operations)
하나의 트랜잭션 내에서 함께 여러 쓰기를 일괄 처리하여 많은 양의 데이터를 기록 할 때 Realm은 매우 효율적이 될 수 있습니다. 트랜잭션은 또한 메인 쓰레드를 막지 않도록 Grand Central Dispatch를 사용하여 백그라운드로 수행 될 수 있습니다. RLMRealm객체는 스레드로부터 안전하지 않는 스레드를 통해 공유 할 수 없습니다, 그래서 여러분은 읽거나 쓰고자 하는 각 쓰레드/dispatch_queue에 RLMRealm인스턴스를 받아야 합니다. 아래는 백그라운드 큐에 백만개의 개체를 입력하는 예입니다:
dispatch_async(queue, ^{
// 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. Order is ignored.
for (NSInteger idx2 = 0; idx2 < 1000; idx2++) {
[Person createInRealm:realm
withObject:@{@"name" : [self randomString],
@"birthdate" : [self randomDate]}];
}
// Commit the write transaction
// to make this data available to other threads
[realm commitWriteTransaction];
}
});
dispatch_async(queue) {
// Get realm and table instances for this thread
let realm = RLMRealm.defaultRealm()
// Break up the writing blocks into smaller portions
// by starting a new transaction
for idx1 in 0..<1000 {
realm.beginWriteTransaction()
// Add row via dictionary. Order is ignored.
for idx2 in 0..<1000 {
Person.createInDefaultRealmWithObject(
["name": "\(idx1)", "birthdate": NSDate(timeIntervalSince1970: idx2)])
}
// Commit the write transaction
// to make this data available to other threads
realm.commitWriteTransaction()
}
}
상세한 내용은 RLMRealm에서 확인 할 수 있습니다.
REST APIs
Realm의 작은 메모리 공간과 빠른 객체 조회로, 여러분은 REST API를 통하여 Realm으로 부터 직접적으로 조회하여 보통의 경우보다 열 배이상의 데이터를 일정하게 조회할수 있습니다. 이것은 아래와 같이 몇가지 장점을 지니고 있습니다.
- 하나의 API 호출로 대량의 데이터를 조회하거나 백그라운드를 통한 프리페칭(pre-fetching)을 Realm을 통해 가능하게 한다.
- Realm은 쓰레드 세이프(thread-safe)이기 때문에 쉽게 작업을 비동기 처리할 수 있고 REST호출이 완료될때 한번에 여러분의 views를 업데이트 할수 있다.
- 서버 측에서 복잡한 쿼리를 처리하는 것 대신에, Realm으로 부터 직접 데이터를 조회할 수 있다.
- 오프라인일때도 대량의 데이터를 저장해 놓을수 있어 조회 및 업데이트를 할 수 있는 강력한 사용자 경험을 제공합니다.
- 서버측의 부하는 줄여줍니다: 처음에는 다소 많은 트래픽을 발생시킬수 있지만, 캐시 데이터를 사용함으로써 반복하여 같은 데이터를 가져오는 빈도를 줄여 서버측에 부하를 줄여주게 됩니다.
모범 사례(Best Practices)
- 비동기 요청 — Realm은 작은 용량으로 대량의 데이터를 보존할 수 있기 때문에, 한가지 좋은 예는 백그라운드로 여러 API요청을 실행 하도록 더욱 큰 데이터 셋을 만드는 것 입니다. 이것은 API요청후 메인 쓰레드로 돌아올때 사용자에게 대기 시간을 경험 하지 않도록 하여 여러분의 앱에서 좀 더 원할한 사용자 경험을 만들어 줄 것입니다. 여러분은 Notifications를 이용하여 REST요청에 대한 감시를 할 수 있습니다.
- 사용자에게 표시되는 데이터보다 큰 데이터셋을 캐싱 — 가능한한 자주 pre-fatch하고 Realm에 로컬로 저장하는 것 을 추천합니다. 예를 들면, 한개의 List View에서 페이지당 10개의 결과를 표시한다면, 사용자가 이후 3-4페이지를 방문하는 것과 같이 더 fetch합니다. 여러분은 지도 보기(주변지역에 대한 화면 데이터를 가져오기)나 여러분의 앱에 대한 일반적인 상황(사용자가 그의 사용성향상 탐색할만한 코스의 화면 데이터를 미리 가져오기)과 같은 것을 고려해야 합니다.
- 입력 또는 갱신 - 여러분의 데이터셋에 프라이머리 키(또는 단일 조건 셋)와 같은 고유 식별자를 가지고 있을때 여러분은 그것을 입력 또는 갱신(insert-or-update)로직으로 사용할수 있습니다: API로부터 결과를 수신할 때 Realm에 각 레코드가 이미 존재하는지 확인 할수 있습니다. 로컬에 존재하는 경우에는 최신의 내용으로 갱신을 하고, 존재하지 않을 경우에는 Realm으로 입력합니다.
Example
아래에는 REST API를 이용하여 Realm을 어떻게 사용할수 있는지에 대한 간단한 예제입니다. 이 예제에서는 foursquare API를 통해서 JSON형식의 데이터를 검색하고, Realm객체를 통해서 기본 Realm에 데이터를 저장할 것 입니다.
실시간으로 동작하는 비슷한 예제는 이곳의 비디오 데모를 통해서 확인이 가능합니다.
먼저 우리는 API를 통해 데이터셋을 가져오기 위해서 기본 Realm의 인스턴스를 생성합니다. 간단하게 이 예제에서 [NSData initWithContentsOfURL]
를 사용하였습니다.
RLMRealm *realm = [RLMRealm defaultRealm];
// 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"];
// Call the API
let url = NSURL(string: "https://api.foursquare.com/v2/venues/search?near=San%20Francisco&limit=50")
let response = NSData(contentsOfURL: url)
// De-serialize the response to JSON
let json = NSJSONSerialization.JSONObjectWithData(response,
options: NSJSONReadingOptions(0),
error: nil)["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를 입력하는 대신에 RLMObjects의 계층구조에 때에 따라 자동적으로 매핑되도록 만들것 입니다. 이 작업을 위해 JSON의 모든 키들에 정확히 매칭시킬 RLMObject구조의 속성이 필요 합니다. JSON키들이 RLMObject의 속성에 매칭되지 않을때에는 입력되지 않을 것입니다. 아래의 RLMObject정의는 정상적으로 동작할 것입니다:
// Venue.h
@interface Venue : RLMObject
@property NSString *id;
@property NSString *name;
@property Contact *contact;
@property Location *location;
@end
RLM_ARRAY_TYPE(Venue)
// Contact.h
@interface Contact : RLMObject
@property NSString *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
RLM_ARRAY_TYPE(Location)
class Contact: RLMObject {
dynamic var phone = ""
}
class Location: RLMObject {
dynamic var lat = 0.0 // latitude
dynamic var lng = 0.0 // longitude
dynamic var postalCode = ""
dynamic var cc = ""
dynamic var state = ""
dynamic var country = ""
}
class Venue: RLMObject {
dynamic var id = ""
dynamic var name = ""
dynamic var contact = Contact()
dynamic var location = Location()
}
결과 셋은 각각의 것를이 [Venue createInDefaultRealmWithObject:]
를 호출하여 만들 것이기 때문에 배열로 반환 됩니다. 이렇게 JSON으로부터 Venue
와 그 자식 객체들이 만들어 지고 이 새롭게 만들어진 객체들은 기본 Realm에 추가 될 것입니다.
//Extract the array of venues from the response
NSArray *venues = json[@"venues"];
[realm beginWriteTransaction];
// Save one Venue object (and dependents) for each element of the array
for (NSDictionary *venue in venues) {
[Venue createInDefaultRealmWithObject:venue];
}
[realm commitWriteTransaction];
//Extract the array of venues from the response
let venues = json["venues"] as [NSDictionary]
realm.beginWriteTransaction()
// Save one Venue object (and dependents) for each element of the array
for venue in venues {
Venue.createInDefaultRealmWithObject(venue)
}
realm.commitWriteTransaction()
마이그레이션(Migrations)
데이터베이스로 작업 할 때, 여러분의 데이터 모델은 시간이 지남에 따라 변경될 것입니다. Realm의 데이터 모델은 표준 객체로 정의되어 있기 때문에 그 데이터 모델을 변경하는 것은 해당 RLMObject의 서브클래스를 변경하는 것 만큼이나 쉽습니다. 예를 들면 아래와 같은 ‘Person.h’가 있다고 가정합니다:
@interface Person : RLMObject
@property NSString *firstName;
@property NSString *lastName;
@property int age;
@end
class Person: RLMObject {
dynamic var firstName = ""
dynamic var lastName = ""
dynamic var age = 0
}
다음에는 성과 이름을 나누는 것보다 전체 이름으로 변경하길 원하면 아래와 같이 간단하게 서브클래스의 인터페이스를 변경하면 됩니다:
@interface Person : RLMObject
@property NSString *fullName;
@property int age;
@end
class Person: RLMObject {
dynamic var fullName = ""
dynamic var age = 0
}
만약 예전 스키마로 된 데이터가 디스크 상에 없다면, 그냥 여러분의 코드를 새로운 정의로 변경 하는것으로 문제 없을 것 입니다. 그러나 코드상에 정의와 Realm에 불일치가 존재하고 디스크상에 데이터를 Realm이 참조 해야 한다면, 즉 만약 여러분의 모델중 하나의 스키마 정의를 변경하고 [RLMRealm defaultRealm]
(또는 유사한 realm 호출)를 통해 영역을 인스턴스화 한다면 이 호출은 메세지와 함께 NSException을 발생시키므로 마이그레이션을 하여야 할 것 입니다.
Realm에 적어도 한개 이상의 재정의된 클래스가 포함되어 있다면, 접근하기 전에 현재의 스키마로 마이그레이션을 해야 합니다. 이 과정을 쉽게 하기 위해 Realm은 스키마 마이그레이션을 위한 특별한 클래스와 메소드를 제공합니다.
Realm을 새로운 스키마로 마이그레이션 하는 것은 단 2단계면 됩니다. 그리고 여러분의 [AppDelegate didFinishLaunchingWithOptions:]
안에서 그것들이 완료되어 있을 것을 권장합니다:
마이그레이션의 실행 (Performing a Migration)
여러분은 RLMMigrationBlock
을 시행하여 마이그레이션을 정의 할때 기본 Realm을 위해서는 [RLMRealm migrateDefaultRealmWithBlock:]
또는 다른 Realm 인스턴스들을 위해서는 [RLMRealm migrateRealmAtPath:withBlock:]
를 호출하여야 합니다. 마이그레이션 블럭은 이전의 스키마로부터 새로운 스키마로 변경하기 위한 모든 로직을 제공합니다.
예를 들면, 이전의 ‘Person’ 서브 클래스를 마이그레이션 한다고 가정합니다. 이를 위한 최소한의 필요한 마이그레이션 블럭은 아래와 같을 것 입니다.
// Inside your [AppDelegate didFinishLaunchingWithOptions:]
RLMMigrationBlock migrationBlock = ^NSUInteger(RLMMigration *migration,
NSUInteger 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
}
// Return the latest version number (always set manually)
// Must be a higher than the previous version or an RLMException is thrown
return 1;
};
// Apply the migration block above to the default Realm
[RLMRealm migrateDefaultRealmWithBlock:migrationBlock];
// Inside your application(application:didFinishLaunchingWithOptions:)
let migrationBlock: RLMMigrationBlock = { migration, oldSchemaVersion in
if oldSchemaVersion < 1 {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
// Return the latest version number (always set manually)
// Must be a higher than the previous version or an RLMException is thrown
return 1
}
// Apply the migration block above to the default Realm
RLMRealm.migrateDefaultRealmWithBlock(migrationBlock)
최소한 해야 할 것은 Realm을 통해 스키마가 (자동으로) 업그레이드 되었는지를 return 1;
을 통해 나타내야 합니다. 주의 반환된 버전의 숫자는 integer(version)이나 timestamp(epoch)가 될 수도 있습니다. 그래서 여러분의 앱안에 사용중인 현재 버전을 수동으로 지정하기를 권고 합니다. 마이그레이션 블록의 마지막에 앱의 버전넘버를 수동으로 반환할 때에는, Realm이 디스크상에 영역에 스키마의 버전 넘버를 갱신 하는것에 주의하여야 합니다.
최소한의 허용 가능한 마이그레이션에 대해서 여러분은 아마도 이 블럭을 통해서 “fullName” 항목에 좀 더 의미있는 내용을 미리 채워넣기를 원할지도 모릅니다. 마이그레이션 블럭내에서 [RLMMigration enumerateObjects:block:]
를 호출하여 특정 유형의 각 Realm객체를 열거하여 필요한 마이그레이션 로직을 적용할수 있습니다.
각각의 열거한 기존의 RLMObject 인스턴스는 oldObject
변수를 통해 접근하여 알 수 있고, 업데이트 된 인스턴스는 newObject
변수를 통해 알수 있습니다:
// Inside your [AppDelegate didFinishLaunchingWithOptions:]
// Perform a migration defining the migration block inline
[RLMRealm migrateDefaultRealmWithBlock:^NSUInteger(RLMMigration *migration, NSUInteger 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"]];
}];
}
// Return the latest version number (always set manually)
// Must be a higher than the previous version or an RLMException is thrown
return 1;
}];
// Inside your application(application:didFinishLaunchingWithOptions:)
// Perform a migration defining the migration block inline
RLMRealm.migrateDefaultRealmWithBlock { migration, oldSchemaVersion in
if oldSchemaVersion < 1 {
// The enumerateObjects:block: method iterates
// over every 'Person' object stored in the Realm file
migration.enumerateObjects(Person.className()) { oldObject, newObject in
// combine name fields into a single field
let firstName = oldObject["firstName"] as String
let lastName = oldObject["lastName"] as String
newObject["fullName"] = "\(firstName) \(lastName)"
}
}
// Return the latest version number (always set manually)
// Must be a higher than the previous version or an RLMException is thrown
return 1
}
마이그레이션이 성공적으로 완료되면, 여러분의 앱을 통해 Realm 및 모든 개체를 평소와 같이 접근 할 수 있습니다.
버전의 추가 (Adding more versions)
여기서 서로 다른 세가지의 스키마를 가진 ‘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
// v0
class Person: RLMObject {
dynamic var firstName = ""
dynamic var firstName = ""
dynamic var age = 0
}
// v1
class Person: RLMObject {
dynamic var fullName = "" // new property
dynamic var age = 0
}
// v2
class Person: RLMObject {
dynamic var fullName = ""
dynamic var email = "" // new property
dynamic var age = 0
}
마이그레이션 블록의 로직은 다음과 같을 것 입니다.
[RLMRealm migrateDefaultRealmWithBlock:^NSUInteger(RLMMigration *migration, NSUInteger 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"] = @"";
}
}];
// Return the latest version number (always set manually)
// Must be a higher than the previous version or an RLMException is thrown
return 2;
}];
RLMRealm.migrateDefaultRealmWithBlock { migration, oldSchemaVersion in
// The enumerateObjects:block: method iterates
// over every 'Person' object stored in the Realm file
migration.enumerateObjects(Person.className()) { oldObject, newObject in
// Add the 'fullName' property only to Realms with a schema version of 0
if oldSchemaVersion < 1 {
let firstName = oldObject["firstName"] as String
let lastName = oldObject["lastName"] as String
newObject["fullName"] = "\(firstName) \(lastName)"
}
// Add the 'email' property to Realms with a schema version of 0 or 1
if oldSchemaVersion < 2 {
newObject[@"email"] = ""
}
}
// Return the latest version number (always set manually)
// Must be a higher than the previous version or an RLMException is thrown
return 2
}
데이터 스키마의 마이그레이션 구현을 더욱 자세한 내용은, 마이그레이션 예제 앱을 확인해 주십시요.
선형 마이그레이션(Linear Migrations)
우리는 이제 우리 앱의 두 사용자 JP와 Tim이 있다고 가정해 봅니다. JP는 매우 자주 업데이트를 합니다만, Tim은 몇몇 버전은 건너뛰었습니다. JP는 우리 앱의 모든 새 버전들의 앱들 보고 매번 스키마를 업그레이드 하였을 것 입니다. 그는 앱을 다운로드 할때마다, v0에서 v1으로 v1에서 v2로 업데이트를 하였을것 입니다. 반면에 Tim이 다운로드를 하였을 경우에는 갑자기 v0에서 v2로 변경하여야 했을 겁니다. 따라서 시작하는 버전에 관계 없도록 비종속적으로 if (oldSchemaVersion < X)
와 같이 모든 업그레이드가 보이도록 여러분의 마이그레이션 블럭을 구조화 해야 합니다.
또 다른 시나리오는 버전을 건너뛰는 사용자에게서 발생 할 수 있습니다. 만약 Version 2에서 “say address”속성을 삭제하고, Version 3에서 다시 재도입 하였고, 사용자는 Version 1에서 Version 3로 건너뛰었을때, 그 코드안의 해당 속성에 대한 스키마와 디스크상의 스키마에 불일치가 없어서 Realm은 “address” 속성의 삭제를 자동적으로 감지하지 못할 것 입니다. Tim의 Person객체는 v3의 address속성은 v1의 address속성을 따라 갈 것입니다.
이것은 여러분이 v1 및 v3사이의 속성에 대해서 내부 저장소의 표현을 변경(말하자면, ISO 주소 표현을 사용자 정의로 변경하는 것과 같은)하지 않는한 문제가 되지 않을 수도 있습니다. 이와 같은 것을 피하기 위해서 모든 버전에 대해서 또는 address속성에 대해 if (oldSchemaVersion < 3)
와 같이 사용하여 모든 realm이 version 3로 업그레이드 되어 정상적인 데이터셋을 가지도록 보장 할 것을 권고 합니다.
추가 지원
여러분은 우리의 예제를 통해서 앱상에서 Realm을 어떻게 사용하는지 확인 할 수 있습니다. (더욱 많은 예제들이 준비되어 있습니다!)
Happy hacking! 여러분들은 언제나 realm-cocoa(영어)에서 실제 개발자들과 대화할 수 있습니다. 또한 Realm 페이스북 그룹 에서 한국 개발자들과 이야기 하고, 한국어로 된 요청사항을 이메일 kr@realm.io 로 보내주세요!
FAQ
Realm 라이브러리는 얼마나 큰가요?
한번 여러분의 앱이 릴리즈를 위해 빌드가 되면, 원래의 사이즈에서 Realm은 오직 1MB 정도만 추가 할 것 입니다. 배포되는 버전은 여러 아키텍쳐들(ARM, ARM64, x86 for the simulator)을 지원하고 디버그 심볼등을 포함하고 있어 상당히 크지만 (각각의 .Framework는 30MB 전후), 여러분의 앱을 빌드하게 되면 Xcode에 의해 자동적으로 제거될 것 입니다.
OS X에 Realm을 사용할 수 있나요?
물론입니다! 사용시에는 소스로 부터 빌드가 필요합니다. 이를 통해 여러분의 앱에서 사용할 수 있도록 OS X호환의 별도의 .Framework를 만들수 있을것 입니다. 또한 CocoaPod에도 이를 추가하기 위해 노력하고 있습니다.
안드로이드도 지원한 예정인가요?
물론입니다! 저희 내부에는 좀 더 튜닝이 필요하지만 거의 동일한 안드로이드용 빌드가 있습니다. 출시 알림을 받기 원하시면, 이 리스트로 여러분을 추가해 주십시요 - 오직 안드로이드판 Realm에 대한 메일만 발송 될 것입니다.
상용 애플리케이션에도 Realm을 사용할 수 있나요?
Realm은 2012년부터 상용 제품에 사용 되어져 왔습니다.
여러분은 우리의 Objective‑C API들이 커뮤니티 피드백으로 개선되어 지는것이 중지 될것을 예상하고 있을 것 입니다. 또한 더욱 기능들과 버그 픽스들이 제공될 것을 예상하고 있을것 입니다. Swift API는 오직 애플에 의해 안정화 된 다음에 사용되어야 할 것입니다; 지금도 우리의 API들은 애플에서 새로운 베타 릴리즈가 될때마다 계속하여 언어가 변경되고 있어 실험적입니다.
Realm을 사용하기 위해 비용을 지불해야 하나요?
아닙니다. iOS판 Realm은 상용 프로젝트 포함하여 사용이 무료입니다.
어떻게 수익을 낼 계획입니까?
우리는 이미 실제로 우리의 기술을 중심으로 기업제품과 서비스를 판매하여 수익을 창출하고 있습니다. 만약 여러분이 릴리즈나 realm-cocoa보다 현재상태에 대해 더욱 알고 싶으시다면, 저희는 이메일로도 대화를 할 수 있습니다. 그리고 저희는 공공의 realm-cocoa를 개발하기 위해, 또한 무료 및 Apache 2.0 라이센스하에 오픈소스로 유지 될 수 있도록 최선을 다하고 있습니다.
저는 코드안에 “tightdb”나 “core”로 참조 되어 있는것을 보았습니다. 이것들은 무엇 인가요?
TightDB는 우리의 핵심 C++ 스토리지 엔진의 옛 이름입니다. 그 코어는 현재 오픈 소스가 아니지만, 한번 내부를 정리하고, 이름을 정리하고, 내부의 동기화 구현을 완료한다면, 역시 Apache 2.0 라이센스로 오픈소스화를 할 계획입니다. 또한, 그 바이너리는 Realm Core(TightDB)의 바이너리 라이센스 내에서 사용 할 수 있습니다.
</div>