You are reading the documentation for an older version of Realm. You can view the latest documentation instead.
开始
安装
手动安装 (Objective‑C & Swift)
- 下载最新的Realm发行版本并在本地解压缩.
- 从
ios/
或者osx/
目录里,把Realm.framework文件拖动到你的Xcode开发项目里的File Navigator 中。 确保 Copy items into destination group’s folder已经被选中,按Finish。 - 在Xcode file explorer中选中你要的开发项目. 选择target,点击 Build Phases 选项. 在Link Binary with Libraries里按+, 添加libc++.dylib.
- 如果使用Realm + Swift, 拖动
Swift/RLMSupport.swift
到你的Xcode project的File Navigator中。点选Copy items if needed - 如果在OSX项目中使用Realm,点击左上角的 + ,选择New Copy Files Phase,将其重命名为Copy Frameworks, 将Destination设置为Frameworks,并且添加
Realm.framework
。
通过CocoaPods安装 (Objective‑C Only)
如果你使用CocoaPods…
- 把
pod "Realm"
添加到你的Podfile中。 - 在命令行中执行
pod install
. - 将CocoaPods生成的
.xcworkspace
运用到你的开发项目中即可。
Xcode 插件
我们的Xcode插件让新建Realm模型(model)很轻松。
安装Realm Xcode插件的最简单的途径就是通过Alcatraz–在”RealmPlugin”目录下。你也可以自行手动安装:打开release zip中的plugin/RealmPlugin.xcodeproj
, 点击编译(build)。 重启Xcode生效。如果你使用Xcode新建文件 (File > New > File… — or ⌘N), 可以看到有一个新建Realm模型(create a new realm model)的选项。
Realm浏览器/数据库管理器
我们另外提供了一个独立的数据库管理工具,用来查看和编辑realm数据库(.realm)。 你可以从browser/
的release zip 目录下找到它。
使用菜单中的工具(tool)>生成演示数据库(generate demo database), 你可以生成一个测试数据库(当然里面的数据是样本数据).
示例
你可以在examples/
路径里面找到一个文件,叫做release zip。 里面包含了Objective‑C, Swift和RubyMotion的示例程序。 它们演示了Realm得很多功能和特性,例如数据库迁移,如何与UITableViewController’s一起使用,加密等等。
获得帮助
- 注册登陆,订阅我们定期发布的community newsletter。里面有一些非常有用的提示和其他的用例。当有新的Realm博客或者教程出现,邮件也会通知你。
- StackOverflow: 查找之前的有#realm标签的问答, — 或者,开一个新的。
- 推特: 联系@realm 或者用#realm标签。
- Email: docs/cocoa/latest realm-cocoa@googlegroups.com.
数据模型(model)
Realm的数据模型是用传统的Objective‑C 接口(interface)和@properties定义的。 就只要定义 ‘RLMObject’的一个子类(subclass)或者一个现成的模型类,你就能轻松创建一个Realm的数据模型对象(data model object)。Realm模型对象和其他的objective-c的功能很相似–你可以给它们添加你自己的方法(method)和协议(protocol)然后和其他的对象一样使用。 唯一的限制就是从它们被创建开始,只能在一个进程中被使用。
如果你已经安装了我们的 Xcode 插件 那么,在“New File”对话框中会有一个很漂亮的样板,你可以用它来创建interface和implementation文件。
你只需要为对象列表添加目标类型的属性(property)或者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> *dogs;
@end
RLM_ARRAY_TYPE(Person) // define RLMArray<Person>
// Implementations
@implementation Dog
@end // none needed
@implementation Person
@end // none needed
import Realm
// Dog model
class Dog: RLMObject {
dynamic var name = ""
dynamic var owner: Person? // Can be optional
}
// Person model
class Person: RLMObject {
dynamic var name = ""
dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
dynamic var dogs = RLMArray(objectClassName: Dog.className())
}
RLMObject的相关细节.
###属性(property)种类
Realm支持以下的属性(property)种类: BOOL, bool, int, NSInteger, long, float, double, CGFloat, NSString, NSDate 和 NSData。
你可以使用RLMArray\<_Object_\>
和 RLMObject
来模拟对一或对多的关系——Realm也支持RLMObject
继承。
属性(property)特性(attributes)
请注意Realm忽略了objective-c的property attributes, 像 nonatomic, atomic, strong, copy, weak 等等。 所以,为了避免误解,我们推荐你在写入模型的时候不要使用任何的property attributes。但是,假如你设置了,这些attributes会一直生效直到RLMObject
被写入realm数据库。 无论RLMObject
在或不在realm中,你为getter和setter自定义的名字都能正常工作。
数据模型定制
Several class methods exist to further specify model information:
+attributesForProperty:
可以被重写来来提供特定属性(property)的属性值(attrbutes)例如某个属性值要添加索引。+defaultPropertyValues
可以被重写,用以为新建的对象提供默认值。+primaryKey
可以被重写来设置模型的主键。定义主键可以提高效率并且确保唯一性。ignoredProperties
可以被重写来防止Realm存储模型属性。
存储对象
对对象的所有更改(添加,修改 和删除)都必须通过写入事务完成。
Rrealm的对象可以被实例化并且被单独使用,和其他常规对象无异。 如果你想要在多个线程中共享或者永久保存以重复使用对象,你必须将其存储到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()
// You only need to do this once (per thread)
// Add to the Realm inside a transaction
realm.beginWriteTransaction()
realm.addObject(author)
realm.commitWriteTransaction()
等到你把这个对象添加到realm数据库里面之后, 你可以在多个线程里面共享之。并且从现在开始,你所做的每一次更改(必须在一个写事务中完成)也会被永久储存。等到写事务完成,这个更改将对所有共享这个Realm数据库的线程可见。
需要注意的是,写入操作会相互阻塞,而且其相对应的进程也会受到影响。这和其他的永久数据存储解决方案是一样的,所以我们建议你使用常用的,也是最有效的方案, 将所有写入放到一个单独的进程中。
还要注意的是,因为realm的MVCC结构, 读取并 不会 因为一个进行中的写事务而受到影响。
查询
所有的数据抓取都很简单,并且直到获得数据之后才创建副本。
关于使用RLMResults的小贴士: Realm的对象查询返回一个RLMResults对象。它包含了一系列的RLMObject。RLMResults有一个与NSArray很相似的interface(接口)并且对象可以通过索引(index)下标获取。 但不同于NSArrays的是,RLMResult是归类的——它只能容纳一种RLMObjects类型。详询RLMResults。
根据种类获取对象
从realm中获取对象的最基本方法就是 [RLMObject allObjects]
, 它返回一个RLMResults,里面是查询的子类的所有RLMObject实例。
// 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
// Query the default Realm
let dogs = Dog.allObjects()
// Query a specific Realm
let petsRealm = RLMRealm(path: "pets.realm")
let otherDogs = Dog.allObjectsInRealm(petsRealm)
谓词/条件查询
如果你对 NSPredicate很熟悉的话, 那么你就已经知道怎么在realm里面查询了。RLMObjects, RLMRealm, RLMArray 和 RLMResults都提供很好的methods来查询特定的RLMObjects:你只需要传递相应地NSPredicate实例,谓词字符串,谓词格式字符串,就可以获取你想要的RLMObjects实例啦。就和NSObject一样的。
举个例子,下面的代码就是对上面的拓展。 通过呼叫[RLMObject objectsWhere:], 获得了默认realm数据库中的所有颜色是黄褐色的,名字开头是“B”的狗的实例。
// Query using a predicate string
RLMResults *tanDogs = [Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"];
// Query using an NSPredicate object
NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@",
@"tan", @"B"];
tanDogs = [Dog objectsWithPredicate:pred];
// Query using a predicate string
var tanDogs = Dog.objectsWhere("color = 'tan' AND name BEGINSWITH 'B'")
// Query using an NSPredicate object
let predicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "tan", "B")
tanDogs = Dog.objectsWithPredicate(predicate)
可以参看Apple的Predicates Programming Guide 了解更多关于如何创建谓词。Realm支持很多常见的谓词:
- 在比较中, 操作数可以是属性名或者常量。但是其中至少有一个是属性名。
- 只有int, long, float, double, and NSDate这些属性类型(property types)支持 ==, <=, <, >=, >, !=, 和 BETWEEN这些比较操作符。
- 布尔属性可以支持==和!=。
- 在NSString和NSData属性中, 我们支持的操作符有 ==, !=, BEGINSWITH, CONTAINS和ENDSWITH。
- realm还支持如下的复合型操作符: AND, OR, NOT
- 注意,我们虽然不支持aggregate expression type,但是我们支持BETWEEN操作符, 例如:
RLMResults *results = [Person objectsWhere:@"age BETWEEN %@", @[42, 43]];
条件排序
在很多情况下,我们都希望获取或者查询返回的结果都能按照一定条件排序。所以,RLMArray支持使用指定的属性对数据列进行排序。Realm允许你指定一个排序要求并且根据一个或多个属性进行排序。举例来说, 下面代码呼叫了[RLMObject objectsWhere:where:]
对返回的数据”dogs”进行排序,排序的条件是名字的字母表升序。:
// Sort tan dogs with names starting with "B" by name
RLMResults *sortedDogs = [[Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"]
sortedResultsUsingProperty:@"name" ascending:YES];
// Sort tan dogs with names starting with "B" by name
var sortedDogs = Dog.objectsWhere("color = 'tan' AND name BEGINSWITH 'B'").sortedResultsUsingProperty("name", ascending: true)
了解更多:[RLMObject objectsWhere:]
和[RLMResults sortedResultsUsingProperty:ascending:]
。
链式查询
Realm查询引擎的一个独特属性就是它能够进行简单快捷的链式查询, 而不需要像传统数据库一样的麻烦。
举个例子来说,假如你要所有黄褐色的小狗的结果序列,然后从中查找名字开头是“B“的小狗。 你可以发送如下的请求。
RLMResults *tanDogs = [Dog objectsWhere:@"color = 'tan'"];
RLMResults *tanDogsWithBNames = [tanDogs objectsWhere:@"name BEGINSWITH 'B'"];
let tanDogs = Dog.objectsWhere("color = 'tan'")
let tanDogsWithBNames = tanDogs.objectsWhere("name BEGINSWITH 'B'")
Realm数据库
默认的realm数据库
你可能已经注意到了我们总是通过[RLMRealm defaultRealm]
来获得realm数据库。 这个method返回一个RLMRealm对象,指向一个叫做”default.realm“的文件。这个文件在你的app的Documents文件夹里面。
其他的realm数据库
有的时候拥有多个分离的数据库是非常有必要的。例如,如果你需要绑定数据到一个App,你可以打开另一个只读的Realm数据库,参考See [RLMRealm realmWithPath:]
和 [RLMRealm realmWithPath:readOnly:error:]
请注意传递到[RLMRealm realmWithPath:]
的路径必须是有写入权限的。存储可写Realm数据库的最常用路径就是iOS的“Documents”和OSX的“Application Support”。
跨线程使用数据库
在不同的线程间使用同一个Realm数据库,你必须呼叫[RLMRealm defaultRealm]
, [RLMRealm realmWithPath:]
或者 [RLMRealm realmWithPath:readOnly:error:]
以得到个线程相对应的realm对象。 只要你路径一致,所有的RLMRealm对象指向的文件就一致。 我们还_不_支持跨线程共享RLMRealm对象。.
纯内存数据库
正常的Realm数据库是存储在硬盘上的, 但你也可以通过使用[RLMRealm inMemoryRealmWithIdentifier:]
来创建一个纯内存数据库。
RLMRealm *realm = [RLMRealm inMemoryRealmWithIdentifier:@"MyInMemoryRealm"];
let realm = RLMRealm.inMemoryRealmWithIdentifier("MyInMemoryRealm")
纯内存数据库在每次程序退出时不会保存数据。但是,这不会妨碍到realm的其他功能,包括请求,关系和线程安全。假如你需要灵活的数据读写但又不想永久储存,那么纯内存数据库对你来说一定大有裨益。
注意: 如果某个纯内存Realm实例没有被引用,所有的数据就会被释放。强烈建议你在app中用强引用来钳制所有新建的春内存Realm数据库。
关系
可以通过使用RLMObject 和 RLMArray<Object> properties互相联结。假如说你预先定义了一个”人“模型(see above),我们再来创建一个”狗“模型。:
// Dog.h
@interface Dog : RLMObject
@property NSString *name;
@end
class Dog: RLMObject {
dynamic var name = ""
}
对一
对于多对一和一对一关系, 仅仅只需要定义一个RLMObject子类类型的property:
// Dog.h
@interface Dog : RLMObject
... // other property declarations
@property Person *owner;
@end
class Dog: RLMObject {
... // other property declarations
dynamic var owner: Person?
}
你可以像往常一样使用之:
Person *jim = [[Person alloc] init];
Dog *rex = [[Dog alloc] init];
rex.owner = jim;
let jim = Person()
let rex = Dog()
rex.owner = jim
当你使用RLMObject properties,你可以通过正常的property语法获取嵌套属性。例如rex.owner.address.country会遍历这个对象图来获得你所想要的子类。
对多
你可以通过使用RLMArray<Object> properties来定义一个对多关系。RLMArrays包含多个同类的RLMObjects,并且拥有一个和NSMutableArray非常相似的interface(接口)。
如果想要把”dogs“(多个”Dog“)属相添加到”Person“模型中来,我们必须定义一个RLMArray
//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())
}
我们又可以像往常一样读取和赋值啦。
// Jim is owner of Rex and all dogs named "Fido"
RLMArray *someDogs = [Dog objectsWhere:@"name contains 'Fido'"];
[jim.dogs addObjects:someDogs];
[jim.dogs addObject:rex];
let someDogs = Dog.objectsWhere("name contains 'Fido'")
jim.dogs.addObjects(someDogs)
jim.dogs.addObject(rex)
通知
每当一次写事务完成Realm实例都会向其他线程上的实例发出通知,可以通过注册一个block来响应通知:
// Observe Realm Notifications
self.token = [realm addNotificationBlock:^(NSString *note, RLMRealm * realm) {
[myViewController updateUI];
}];
// Observe Realm Notifications
let token = realm.addNotificationBlock { note, realm in
viewController.updateUI()
}
只要有任何的引用指向这个返回的notification token,它就会保持激活状态。在这个注册更新的类里,你需要有一个强引用来钳制这个token, 因为一旦notification token被释放,通知也会自动解除注册。
具体内容:[Realm addNotificationBlock:]
和 [Realm removeNotificationBlock:]
。
后台操作
通过把大量的写入放入到一个大型事务中,Realm可以大大的提高大型数据读取的运行效率。事务可以在后台通过GCD运行,这样可以避免阻塞主进程。 RLMRealm并不是线程安全的,所以你必须在每一个你需要读写的进程或者调度队列中添加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. Property 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. Property 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在几个方面胜过了无本地缓存的REST API:
- Realm缓存数据使得你能提供离线体验,普通的REST API无法做到这一点——他们一定需要网络连接。
- 通过在本地存储你的整个数据集,你可以在本地进行查询,这能提供比普通REST API好很多的本地搜索体验。
- 可直接从Realm查询数据,不必等待服务器端复杂的API处理。
- 减轻服务器端负荷,只需要在更新和修改数据时进行必要的访问。
最佳操作
- 异步请求 — 网络请求和其他一些操作应该放到后台,以免影响交互效果。同理Realm数据库中大规模插入和修改应该在后台进行。你可以用通知来相应后台操作。
- 缓存数据库大于用户当下查询 — 我们建议你对可能使用的数据进行预处理并且存储到Realm中。 这么做可以让你在本地数据集中进行查询。
- 插入或更新 — 如果你的数据集有一个特有的标识符, 例如一个主键, 你可以用它来判定插入还是更新。只需要使用
[RLMObject createOrUpdateInDefaultRealmWithObject:]
:如果你从API得到响应, 这个method会从Realm中查询这个响应是否有记录。 如果在本地有记录, 就可以从响应中根据最新的数据进行更新。如果没有,就将该响应插入到Realm数据库中。
示例
以下是一个如何应用一个使用了REST API的Realm的示例。在这个示例里,我们将从foursquare API里获取一组JSON格式的数据,然后将它以Realm Objects的形式储存到默认realm数据库里。 如果你想参考类似示例的实际操作,请观看 video demo.
首先我们要创建一个默认Realm数据库的实例,用于存储数据以及从 API 获取数据。为了更简单易读,我们在这个例子里面运动了 [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"];
// 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"
}
}
]
}
要想把JSON数据导入Realm中我们有很多办法,殊途同归。你可以读取 NSDictionary然后将其属性通过插入功能手动映射到一个RLMObject上。为了演示效果,在这个示例里,我们将直接把 NSDictionary插入到Realm中,然后让Realm自动快速将其属性映射到RLMObject上。为了确保示例能够成功,我们需要一个属性完全匹配JSON数据特点的RLMObject的框架。JSON数据特点如果得不到匹配,将在植入时自动被忽略。 以下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)
class Contact: RLMObject {
dynamic var phone = ""
class func primaryKey() -> String! {
return "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()
class func primaryKey() -> String! {
return "id"
}
}
因为结果集是以数组的形式给我们的,我们要呼叫 [Venue createInDefaultRealmWithObject:]
来为每个元素创建一个对象. 这里会创建 Venue
和一个JSON格式的子对象,并将这些新建的对象加入到默认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 createOrUpdateInDefaultRealmWithObject:venue];
}
[realm commitWriteTransaction];
//Extract the array of venues from the response
let venues = json["venues"] as [NSDictionary]
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
// Save one Venue object (and dependents) for each element of the array
for venue in venues {
Venue.createOrUpdateInDefaultRealmWithObject(venue)
}
realm.commitWriteTransaction()
数据库迁移
当你和数据库打交道的时候,时不时的你需要改变数据模型(model),但因为Realm中得数据模型被定义为标准的Objective‑C interfaces,要改变模型,就像改变其他Objective‑C interface一样轻而易举。举个例子,假如我们有如下的interface, 叫“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
}
我们想要更新数据模型,因为我们要添加一个“全名”(fullname)属性, 而不是用分开的“姓”+“名”。要达到这样的目的,我们只需要改变对象的interface,如下:
@interface Person : RLMObject
@property NSString *fullName;
@property int age;
@end
class Person: RLMObject {
dynamic var fullName = ""
dynamic var age = 0
}
在这个时候如果你保存了数据,那么Realm就会注意到代码和硬盘数据不匹配。 每当这时,你必须对数据构架进行迁移,否则就会有错误抛出。
进行迁移
你可以通过呼叫[RLMRealm setSchemaVersion:withMigrationBlock:]
自定义数据迁移以及相应的构架版本。你的数据迁移模块将会为你提供相应地逻辑,用来更新数据构架。呼叫[RLMRealm setSchemaVersion:withMigrationBlock:]
之后, 任何需要迁移的Realm数据库都会自动使用指定的迁移模块并且更新到相应地版本。
例如,假设我们想要把之前‘Person’的子类迁移,如下所示是最简化的数据迁移组:
// Inside your [AppDelegate didFinishLaunchingWithOptions:]
// Notice setSchemaVersion is set to 1, this is always set manually. It must be
// higher than the previous version (oldSchemaVersion) or an RLMException is thrown
[RLMRealm setSchemaVersion:1
forRealmAtPath:[RLMRealm defaultRealmPath]
withMigrationBlock:^(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
}
}];
// now that we have called `setSchemaVersion:withMigrationBlock:`, opening an outdated
// Realm will automatically perform the migration and opening the Realm will succeed
[RLMRealm defaultRealm];
// Inside your application(application:didFinishLaunchingWithOptions:)
// Notice setSchemaVersion is set to 1, this is always set manually. It must be
// higher than the previous version (oldSchemaVersion) or an RLMException is thrown
RLMRealm.setSchemaVersion(1, forRealmAtPath: RLMRealm.defaultRealmPath(),
withMigrationBlock: { migration, oldSchemaVersion in
// 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
}
})
// now that we have called `setSchemaVersion:withMigrationBlock:`, opening an outdated
// Realm will automatically perform the migration and opening the Realm will succeed
// i.e. RLMRealm.defaultRealm()
我们所需要做的就是用一个空模块更新版本,表明这个构架已经被Realm自动更新了。
虽然这是系统能接受的最简化的迁移,我们应当用有意义的代码来填充这些新的属性(这里就是“fullname”)。在数据迁移模块中,我们可以呼叫[RLMMigration enumerateObjects:block:]
来列举某种格式的每一个Realm文件,并执行必要的迁移判定:
// Inside your [AppDelegate didFinishLaunchingWithOptions:]
[RLMRealm setSchemaVersion:1
forRealmAtPath:[RLMRealm defaultRealmPath]
withMigrationBlock:^(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"]];
}];
}
}];
// Inside your application(application:didFinishLaunchingWithOptions:)
RLMRealm.setSchemaVersion(1, forRealmAtPath: RLMRealm.defaultRealmPath(),
withMigrationBlock: { 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)"
}
}
})
一旦迁移成功结束,Realm和其所有文件即可被你的app正常存取。
添加更多的版本
假如说现在我们有两个之前版本的_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 setSchemaVersion:2 forRealmAtPath:[RLMRealm defaultRealmPath]
withMigrationBlock:^(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"] = @"";
}
}];
}];
// now that we have called `setSchemaVersion:withMigrationBlock:`, opening an outdated
// Realm will automatically perform the migration and opening the Realm will succeed
[RLMRealm defaultRealm];
RLMRealm.setSchemaVersion(2, forRealmAtPath: RLMRealm.defaultRealmPath(),
withMigrationBlock: { 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"] = ""
}
}
})
// Realm will automatically perform the migration and opening the Realm will succeed
let realm = RLMRealm.defaultRealm()
想要了解更多关于数据库的框架迁移, 参见migration sample app.
Linear Migrations
假如说,我们的app有两个用户: JP和Tim. JP经常更新app,但Tim却经常跳过。 所以JP可能下载过这个app的每一个版本,并且一步一步的跟着更新构架:他下载第一次更新,从v0到v1, 第二次更新从v1到v2,以此类推,井然有序。相反,Tim很有可能直接从v0跳到了v2。 所以,你应该使用非嵌套的 if (oldSchemaVersion < X)
结构来构造你的数据库迁移模块,以确保不管他们是在使用哪个版本的构架,都能看见所有的更新。
当你的用户不按规则出牌,跳过有些更新版本的时候,另一种情况也会发生。 假如你在v2里删掉了一个“email”属性,然后在v3里又把它重新引进了。假如有个用户从v1直接跳到v3,那Realm不会自动检测到v2的这个删除操作因为存储的数据构架和代码中的构架吻合。这会导致Tim的Person对象有一个v3的email property,但里面的内容却是v1的。这个看起来没什么大问题,但是假如两者的内部存储类型不同(比如说: 从ISO email representation 变成了自定义),那麻烦就大了。为了避免这种不必要的麻烦,我们推荐你在if (oldSchemaVersion < 3)
中,nil out所有的email property。
下一步
你可以看一下我们的我们给出的示例。看看在app中应该如何使用realm(我们已经有越来越多的样本了!)
做一个愉快地码农!你也总是可以在realm-cocoa上实时的和其他开发者聊天。
当前的限制
realm现在还是beta版本。我们还在为1.0的发布一直不断的添加新特性,修复bug。我们整理了一些普遍存在的限制
如果你想要看到完整地issue列表, 参见GitHub issues。
Realm CocoaPods 不支持Swift 项目开发
CocoaPods 暂时还不支持Swift 项目开发(戳这个GitHub issue #2222). 想要在一个Swift 项目中使用Realm,请参见上面的步骤.
暂时不支持通知细节
虽然要在realm发生变化的时候可以接到通知 (参见 通知), 但现在我们还不能从notification里面得知什么东西被添加/删减/移动/更新了。 我们会尽快完善这个功能的。
NSDate在秒的地方被截断
一个包含非整秒数的NSDate在存入realm的时候,会在秒的地方被截断。我们正在修复这个问题。 可参考 GitHub issue #875。同时,你可以无损存储NSTimeInterval格式。
Realm对象的Setters & Getters不能被重写
因为Realm重写了setters和getters, 所以你不可以在你的对象上再重写。一个简单的替代方法就是:创建一个新的realm-ignored属性(它的accessors可以被重写, 并且可以呼叫其他的getter和setter)。
不支持 KVO
Realm不支持KVO, 但它有自己的通知机制(see 通知).
Realm文件不能被两个进程同时访问
尽管Realm文件可以在多个线程中被同时访问, 它们每次只能被一个进程访问。这对iOS 8和OSX应用有影响。不同的进程应该复制或者新建Realm文件。 敬请期待多进程支持。
FAQ
realm的支持库有多大?
一旦你的app编译完成, realm的支持库应该只有1 MB左右。 我们发布的那个可能有点大(iOS ~37MB, OSX ~2.4MB), 那是因为它们还包含了对其他构架的支持(ARM, ARM64,模拟器的是X86)和一些编译符号。 这些都会在你编译app的时候被Xcode自动清理掉。
我应该在正式产品中使用realm吗?
自2012年起, realm就已经开始被用于正式的商业产品中了。
正如你预期,我们的objective-c & Swift API 会随着社区的反馈不断的完善和进化。 所以,你也应该期待realm带给你更多的新特性和版本修复。
我该如何保护Realm里面的数据?
Realm 有几种加密方法, 各有千秋。参考这个GitHub评论。Realm将在未来支持跨平台加密。
我要付realm的使用费用吗?
不要, Realm的彻底免费的, 哪怕你用于商业软件。
你们计划怎么赚钱?
其实,我们靠着我们的技术,已经开始赚钱啦!这些钱来自于我们销售企业级产品的利润。如果你想要得到比普通发行版本或者realm-cocoa更多的支持, 我们很高兴和你发邮件聊聊。 我们一直致力于开发开源的(Apache 2.0),免费的realm-cocoa。
我看到你们在代码里有“tightdb”或者“core”, 那是个啥?
TightDB是我们的C++存储引擎的旧名。core 现在还没有开源但我们的确想这样做(依旧使用Apache2.0)假如我们有时间来进行清理,重命名等工作。同时,它的二进制发行版在Realm Core(tightDB)Binary License里面可以找到。