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

开始

安装

手动安装

  1. 下载最新的Realm发行版本并在本地解压缩.
  2. ios/或者osx/ 目录里,把Realm.framework文件拖动到你的Xcode开发项目里的Frameworks文件夹中。 确保 Copy items into destination group’s folder已经被选中,按Finish
  3. 在Xcode file explorer中选中你要的开发项目. 选择target,点击 Build Phases 选项. 在Link Binary with Libraries里按+, 添加libc++.dylib.

通过CocoaPods安装

如果你使用CocoaPods

  1. pod "Realm"添加到你的Podfile中。
  2. 在命令行中执行pod install.
  3. 将CocoaPods生成的.xcworkspace运用到你的开发项目中即可。
  1. github.com/realm/realm-cocoa下载源代码
  2. Realm-Xcode6.xcodeproj 文件拖动到你的 Xcode 开发项目中。 另外,确保你使用的是xcode6 bata5或者更高版本。(Realm不支持Xcode6.1).
  3. 在你的app target的”Build Phases”中, 展开”Target Dependencies” 单击 + (2) (click any of the images to get a large version)
  4. 从目标列表中选择“iOS“
  5. 展开 “Link Binary with Libraries” 单击”+“符号 + 选择 Realm.framework (iOS)
  6. 在左上角单击 + 添加 “New Copy Files Phase”
  7. 在新的 “Copy Files Phase”, 把 ”destination“选项的值从”Resources“改为”Frameworks“
  8. 在”Copy Files Phase“选项中, 单击”+“符号, 选择”Realm.framework“
  9. Xcode会同时自动加载”OSX“和”IOS“两个版本的Realm。删除掉那个描述后面没有”iphoneos“的版本。
  10. 切换到 Build Settings, 将”Defines Module“设置为”YES“
  11. (从你的Realm-Xcode6.xcodeproj > Realm > Swift中)把RLMSupport.swift拖动到你的main project中并把它添加到你的application’s target(s)
  12. 编译运行即可。

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), 你可以生成一个测试数据库(当然里面的数据是假的).

如果你已经安装了Xcode6 beta 或者运行 OSX 新版本 Yosemite(优胜美地),当你启动app的时候,可能会遇到 ”身份不明开发者“(unidentified developer)错误提示。这是苹果的beta版本的bug。 在这种情况之下, 右击app并选择“打开”(open)。 你还是会遇到一个错误警告, 但你可以看到有一个“open the app anyway”的选项。 双击之就不会有任何错误提示(异常感谢@apalancat!)

示例

你可以在ios/examples/路径里面找到一个文件,叫做release zip。 里面包含了objective-c的示例程序。 它们演示了Realm得很多功能和特性,例如数据库迁移,UITableViewController’s,加密等等。 ### 获得帮助

  • 注册登陆,订阅我们定期发布的community newsletter。里面有一些非常有用的提示和其他的用例。当有新的Realm博客或者教程出现,邮件也会通知你。
  • StackOverflow: 查找之前的有#realm标签的问答, — 或者,开一个新的
  • 推特: (不会翻墙的大陆人民请无视)联系@realm 或者用#realm标签。
  • Email: [email protected].

数据模型(model)

Realm的数据模型是用传统的NSObject-style类,加上@properties定义的。 就只要定义 RLMObject的一个子类(subclass),你就能轻松创建一个Realm的数据模型对象(data model object)。Realm模型对象和其他的objective-c的功能很相似–你可以给它们添加你自己的方法(method)和协议(protocol)然后和其他的对象一样使用。 唯一的限制就是从它们被创建开始,只能在一个进程中被使用。

如果你已经安装了我们的 Xcode 插件 那么,在“New File”对话框中会有一个很漂亮的样板,你可以用它来创建interface和implementation文件。

你只需要为对象列表添加目标类型的属性(property)或者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)种类

Realm支持以下的属性(property)种类: BOOL, bool, int, NSInteger, long, float, double, CGFloat, NSString, NSDate 和 NSData。

模型(model)对象同样支持id的属性(properties),同时可以存储NSString, NSDate和NSData。

另外模型关系也支持RLMObject和RLMArray的子类

属性(property)特性(attributes)

请注意Realm忽略了objective-c的property attributes, 像 nonatomic, atomic, strong, copy, weak 等等。 所以,为了避免误解,我们推荐你在写入模型的时候不要使用任何的property attributes。但是,假如你设置了,这些attributes会一直生效直到RLMObject被写入realm数据库。 无论RLMObject在或不在realm中,你为getter和setter自定义的名字都能正常工作。

数据库写入

对对象的所有更改(添加,修改 和删除)都必须通过写入事务完成。

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()

// Add to the Realm inside a transaction
realm.beginWriteTransaction()
realm.addObject(author)
realm.commitWriteTransaction()

等到你把这个对象添加到realm数据库里面之后, 你可以在多个线程里面共享之。你所做的每一次更改也会保留。

需要注意的是,写入操作会相互阻塞,而且其相对应的进程也会受到影响。这和其他的永久数据存储解决方案是一样的,所以我们建议你使用常用的,也是最有效的方案, 将所有写入放到一个单独的进程中。

还要注意的是,多亏了realm的MVCC结构, 读取并 不会 因为一个进行中的写事务而受到影响!

详细内容,请阅读RLMRealmRLMObject

查询

所有的数据抓取都很简单,数据从来都不需要副本。

关于使用Realm 数组(RLMArray)的小贴士: 所有成功的数据获取或者查询都会以RLMArray的形式返回一组RLMObjects。RLMArray操作起来和NSArray没啥区别;但是,RLMArrays 是归类的,换句话说一个RLMArrays里面只能存储一种RLMObject子类。如需更多详情,参见RLMArray.

根据种类获取对象

从realm中获取对象的最基本方法就是 [RLMObject allObjects], 它会从默认的realm数据库中返回这个子类的所有 RLMObject实例(instance)

// 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)

谓词/条件查询

如果你对 NSPredicate很熟悉的话, 那么你就已经知道怎么在realm里面查询了。RLMObjects, RLMRealm 和 RLMArray都提供很好的methods:你只需要传递相应地NSPredicate实例, 谓词字符串,谓词格式字符串,就可以获取你想要的RLMObjects实例啦。就和NSObject一样的。

举个例子,下面的代码就是对上面的拓展。 通过呼叫[RLMObject objectsWhere:], 获得了默认realm数据库中的所有颜色是黄褐色的,名字开头是“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)

可以参看Apple的Predicates Programming Guide 了解更多关于如何创建谓词。这里有一些谓词查询的细节:

  • 在比较中, 操作数可以是属性名或者常量。但是其中至少有一个是属性名。
  • 只有int, long, float, double, and NSDate这些属性类型(property types)支持 ==, <=, <, >=, >, !=, 和 BETWEEN这些比较操作符。
  • 布尔属性可以支持==和!=。
  • 在NSString和NSData属性中, 我们支持的操作符有 ==, !=, BEGINSWITH, CONTAINS和ENDSWITH。
  • realm还支持如下的复合型操作符: AND, OR, NOT
  • 注意,我们虽然不支持aggregate expression type,但是我们支持BETWEEN操作符, 例如:RLMArray *results = [Person objectsWhere:@"age BETWEEN %@", @[42, 43]];

详询[RLMObject objectsWhere:].

条件排序

在很多情况下,我们都希望获取或者查询返回的结果都能按照一定条件排序。所以,RLMArray支持使用指定的属性对数据列进行排序。

我们举例来说,下面代码呼叫了[RLMObject objectsWhere:where:]对返回的数据”dogs”进行排序,排序的条件是名字的字母表升序。:

// 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:].

链式查询

使用Realm存储应用数据的另一个优点就是可以很快的进行链式查询,而不需要像传统数据库一样的麻烦。

举个例子来说,假如你要所有黄褐色的小狗的结果序列,然后从中查找名字开头是“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'")

realm数据库

默认的realm数据库

你可能已经注意到了我们总是通过[RLMRealm defaultRealm]来获得realm数据库。 这个method返回一个RLMRealm对象,指向一个叫做”default.realm“的文件。这个文件在你的app的Documents文件夹里面。所有的写事务都被写到这个地方,读也是同理。

其他的realm数据库

有的时候拥有多个分离的数据库是非常有必要的。比如数据分组,或者按照app的功能整理数据库,或者你想要一些只读的文件,但它们又必须和你其他的可写数据库区分开。 参考 [RLMRealm realmWithPath:][RLMRealm realmWithPath:readOnly:error:]

跨线程使用数据库

如果你想要在几个不同的线程之间使用同一个realm数据库,你必须要使用[RLMRealm defaultRealm], [RLMRealm realmWithPath:]或者[RLMRealm realmWithPath:readOnly:error:] 以得到个线程相对应的realm对象。 只要你路径一致,所有的RLMRealm对象指向的文件就一致。 一定_不要_ 跨线程共享RLMRealm对象。.

默认realm的纯内存数据库

默认realm数据库是默认保存在硬盘上的, 但是你也可以在呼叫[RLMRealm defaultRealm]之前,执行如下的代码–这样你就可以只在内存中使用realm数据库了。

[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的其他功能,包括请求,关系和线程安全。假如你需要灵活的数据读写但又不想永久储存,那么纯内存数据库对你来说一定大有裨益。

注意: 假如纯内存数据库没有被引用的话,所有的数据就会被释放。(我们正在考虑改善API和语义让这个功能更加的直观)。

关系

任何两个RLMObjects都可以以某种方式被联结起来。假如说你预先定义了一个”人“模型(see above),我们再来创建一个”狗“模型。:

// Dog.h
@interface Dog : RLMObject
@property NSString *name;
@end
class Dog: RLMObject {
    dynamic var name = ""
}

多对一

仅仅只需要给你的一个RLMObject子类添加一个属性:

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

当你在查询的时候,子类是不被放入内存的,但你可以通过呼叫,例如rex.owner.address.country,来获得你所想要的子类。

多对多

你可以通过RLMArray<Object> 定义,让一个对象和多个对象之间建立联系。 RLMArrays其实就是RLMObjects的容器,和NSArray很相似,不同之处就是前者归类。

如果想要把”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 addObjectsFromArray:someDogs];
[jim.dogs addObject:rex];
let someDogs = Dog.objectsWhere("name contains 'Fido'")
jim.dogs.addObjectsFromArray(someDogs)
jim.dogs.addObject(rex)

注意: 数据模型的RLMArray属性是写时复制的。任何的直接对该属性的赋值将会从被赋值对象复制参数到该对象的相对属性。 在之前的实例中,刚刚提到的操作意味着,任何在jim.dogs = some_dogs;之后添加到some_dogs的dogs不会被添加到jim.dogs中。

通知

当Realm更新的时候会发出通知,可以通过注册一个block来响应通知:

// Observe Realm Notifications

self.token = [realm addNotificationBlock:^(NSString *note, RLMRealm * realm) {
    [myViewController updateUI];
}];
let token = realm.addNotificationBlock { note, realm in
    viewController.updateUI()
}

你可以用一个token来取消订阅通知。我们推荐你把这个token放到你的main class里面 因为一旦你丢失了这个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. 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具有内存占用率低和查询快速等优点, 你可以直接从Realm读取数据,而且这些数据可以高达通常情况下从REST API所得到的数据的10倍。 它具有如下优点: - 可以通过一个简单的API呼叫或者后台预处理来获得大量数据并保存在Realm中。 - 因为Realm是线程安全的,所以你可以异步这项任务, 并在REST呼叫完成时立即更新视图。 - 可直接从Realm查询数据,不必等待服务器端复杂的API处理。 - 提供更好的用户体验。因为你可以缓存大量的数据,并在无网络情况下在这些数据中进行查询和更新。 - 减轻服务器端负荷:虽然开始几次使用会产生比往常大的流量,但如果用户长期使用的话,缓存的数据会帮助减轻服务器端的负荷,因为你再也不需要为了相同的数据重复的访问服务器。

最佳操作

  1. 异步请求 — 因为Realm可以在占用内存很小的情况下处理大量的数据, 所以一个比较好的做法是通过在后台处理多个API请求建立一个较大的本地数据库。这样做可以为你的app提供一个无痕流畅的用户体验,因为用户不用在主进程等待从API获取的数据。你可以使用 通知 来检测你的 REST 请求进程。
  2. 缓存数据库大于用户当下查询 — 我们推荐你尽可能经常的预处理数据并储存在Realm中。举例来说,如果你每个列表页只列出10项,在用户可能继续浏览的情况下建议你预先获取3-4页的数据。 你可以考虑在景观图(获取周边地带的地图)或你的app的其他通用功能(预处理用户平时使用时会浏览的页面)上使用这个操作。
  3. 插入或更新 — 如果你的数据集有一个特有的标识符(或一组单一的条件), 你可以轻松的用它来判定插入还是更新:如果你从API得到响应,你可以从Realm中查询这个响应是否有记录。如果在本地有记录,就可以从响应中根据最新的数据进行更新。如果没有,就将该响应插入到Realm数据库中。

示例

以下是一个如何应用一个使用了REST API的Realm的示例。在这个示例里,我们将从foursquare API里获取一组JSON格式的数据,然后将它以Realm Objects的形式储存到默认realm数据库里。 如果你想参考类似示例的实际操作,请观看 video demo.

首先我们要创建一个默认Realm数据库的实例,用于存储数据以及从 API 获取数据。为了更简单易读,我们在这个例子里面运动了 [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"
      }
    }
  ]
}

要想把JSON数据导入Realm中我们有很多办法,殊途同归。你可以读取 NSDictionary 然后将其属性通过插入功能手动映射到一个 RLMObject 上。为了演示效果,在这个示例里,我们将直接把 NSDictionary插入到Realm中,然后让Realm自动快速将其属性映射到RLMObject上。为了确保示例能够成功,我们需要一个属性完全匹配JSON数据特点的RLMObject的框架。JSON数据特点如果得不到匹配,将在植入时自动被忽略。 以下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:] 来为每个元素创建一个对象. 这里会创建 Venue 和一个JSON格式的子对象,并将这些新建的对象加入到默认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()

数据库迁移

当你和数据库打交道的时候,时不时的你需要改变数据模型(model),但因为Realm中得数据模型被定义为标准的对象,要改变它们,就像改变相应RLMObject 子类的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 defaultRealm] (或其他方式), 那么你就会得到一个 NSException,提示你需要迁移

realm里面假如有一个类被重新定义了,那么你就必须要将其迁移到现有的数据构架,否则讲不能读写。为了让这个过程更简单,realm提供了专门的类和方法来处理数据构架迁移。

虽然迁移一个realm数据库只需要两步,但你必须将其优先完成,不可以马虎。 我们建议你在你的[AppDelegate didFinishLaunchingWithOptions:]中完成。

进行迁移

你可以自定义数据迁移。对于默认Realm利用RLMMigrationBlock呼叫[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)

将步骤最简化,我们只需要用return 1;来表示Realm已经将构架自动转换更新。 N.B. 回应的新版本的标号可以是一个数字(版本)或者时间戳(时期)。 我们建议你在代码中手动设置,这个标号将代表你的app现在应用的构架的版本。 虽然当你进行完数据迁移时你必须手动向app回应版本标号,Realm将会帮助你在硬盘中更新新的构架版本标号。

虽然这是系统能接受的最简化的迁移,我们应当用更有意义的代码来替代以下代码填进“fullName”属性。在数据迁移模块中,我们可以呼叫[RLMMigration enumerateObjects:block:] 来列举某种格式的每一个Realm文件,并执行必要的迁移判定:

// 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和其所有文件即可被你的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 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
}

想要了解更多关于数据库的框架迁移, 参见migration sample app.

Linear Migrations

假如说,我们的app有两个用户: JP和Tim. JP经常更新app,但Tim却经常跳过。 所以JP可能下载过这个app的每一个版本,并且一步一步的跟着更新构架:他下载第一次更新,从v0到v1, 第二次更新从v1到v2,以此类推,井然有序。相反,Tim很有可能直接从v0跳到了v2。 所以,你应该使用非嵌套的 if (oldSchemaVersion < X)结构来构造你的数据库迁移模块,以确保不管他们是在使用哪个版本的构架,都能看见所有的更新。

当你的用户不按规则出牌,跳过有些更新版本的时候,另一种情况也会发生。 假如你在v2里删掉了一个“address”属性,然后在v3里又把它重新引进了。假如有个用户从v1直接跳到v3,那Realm不会自动检测到v2的这个删除操作因为存储的数据构架和代码中的构架吻合。这会导致Tim的Person对象有一个v3的address property,但里面的内容却是v1的。这个看起来没什么大问题,但是假如两者的内部存储类型不同(比如说: 从ISO address representation 变成了自定义),那麻烦就大了。为了避免这种不必要的麻烦,我们推荐你在if (oldSchemaVersion < 3)中,nil out所有的address property。

下一步

你可以看一下我们的我们给出的示例。看看在app中应该如何使用realm(我们已经有越来越多的样本了!)

做一个愉快地码农!你也总是可以在realm-users上实时的和其他开发者聊天。

当前的限制

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格式。

不支持继承

你只能从RLMObject直接继承,来生成一个Realm对象. 因此, Realm object不可以相互继承。 参考GitHub issue #652

Realm对象的Setters & Getters不能被重写

因为Realm重写了setters和getters, 所以你不可以在你的对象上再重写。一个简单的替代方法就是:创建一个新的realm-ignored属性(它的accessors可以被重写, 并且可以呼叫其他的getter和setter)。

不支持 KVO

Realm不支持KVO, 但它有自己的通知机制(see 通知).

FAQ

realm的支持库有多大?

一旦你的app编译完成, realm的支持库应该只有1 MB左右。 我们发布的那个可能有点大(每个.framework文件都有30MB左右), 那是因为它们还包含了对其他构架的支持(ARM, ARM64,模拟器的是X86)和一些编译符号。 这些都会在你编译app的时候被Xcode自动清理掉。

我可以在OS X app里使用 Realm吗?

可以! 现在, 你只需要编译源代码,就可以得到一个单独的 与OS X兼容的.framework。 然后你就可以在你的app里面使用它了。 现在,我们还在试图支持CocoaPod。

你们会支持安卓吗?

绝对的! 我们已经有一个内部的成熟的安卓版本。 但我们还在调整。 想要第一时间知道安卓版本的发布的话,你可以把自己添加到这个列表里面 — 我们保证,除了通知你安卓版本的发布,我们绝对不会给你发邮件骚扰你。

我应该在正式产品中使用realm吗?

自2012年起, realm就已经开始被用于正式的商业产品中了。

正如你预期,我们的objective-c API 有的时候会挂掉。但社区的反馈让我们一直在不断的学习和完善。 所以,你也应该期待realm带给你更多的新特性和版本修复。 Swift API 理论上只能在Apple发布稳定版本之后才能正式用于产品中。 现在我们的API还只是试验性质的因为Apple一直在不断的改变这个语言。

我要付realm的使用费用吗?

不要, realm for IOS的彻底免费的, 哪怕你用于商业软件。

你们计划怎么赚钱?

其实,我们靠着我们的技术,已经开始赚钱啦!这些钱来自于我们销售企业级产品的利润。如果你想要得到比普通发行版本或者realm-cocoa更多的支持, 我们很高兴和你发邮件聊聊。 我们一直致力于开发开源的(Apache 2.0),免费的realm-cocoa

我看到你们在代码里有“tightdb”或者“core”, 那是个啥?

TightDB是我们的C++存储引擎的旧名。core 现在还没有开源但我们的确想这样做(依旧使用Apache2.0)假如我们有时间来进行清理,重命名等工作。同时,它的二进制发行版在Realm Core(tightDB)Binary License里面可以找到。