You are reading the documentation for an older version of Realm. You can view the latest documentation instead.

开始

安装

手动安装 (Objective‑C & Swift)

  1. 下载最新的Realm发行版本并在本地解压缩.
  2. ios/或者osx/ 目录里,把Realm.framework文件拖动到你的Xcode开发项目里的File Navigator 中。 确保 Copy items into destination group’s folder已经被选中,按Finish
  3. 在Xcode file explorer中选中你要的开发项目. 选择target,点击 Build Phases 选项. 在Link Binary with Libraries里按+, 添加libc++.dylib.
  4. 如果使用Realm + Swift, 拖动Swift/RLMSupport.swift到你的Xcode project的File Navigator中。点选Copy items if needed
  5. 如果在OSX项目中使用Realm,点击左上角的 + ,选择New Copy Files Phase,将其重命名为Copy Frameworks, 将Destination设置为Frameworks,并且添加 Realm.framework

通过CocoaPods安装 (Objective‑C Only)

如果你使用CocoaPods

  1. pod "Realm"添加到你的Podfile中。
  2. 在命令行中执行pod install.
  3. 将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 [email protected].

数据模型(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结构, 读取并 不会 因为一个进行中的写事务而受到影响。

详细内容,请阅读RLMRealmRLMObject

查询

所有的数据抓取都很简单,并且直到获得数据之后才创建副本。

关于使用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]];

详询[RLMObject objectsWhere:].

条件排序

在很多情况下,我们都希望获取或者查询返回的结果都能按照一定条件排序。所以,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 ————这可以通过在相应的模型(model)interface中添加一个宏(macro)来实现。

//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处理。 - 减轻服务器端负荷,只需要在更新和修改数据时进行必要的访问。

最佳操作

  1. 异步请求 — 网络请求和其他一些操作应该放到后台,以免影响交互效果。同理Realm数据库中大规模插入和修改应该在后台进行。你可以用通知来相应后台操作。
  2. 缓存数据库大于用户当下查询 — 我们建议你对可能使用的数据进行预处理并且存储到Realm中。 这么做可以让你在本地数据集中进行查询。
  3. 插入或更新 — 如果你的数据集有一个特有的标识符, 例如一个主键, 你可以用它来判定插入还是更新。只需要使用[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里面可以找到。