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

如果你打算使用Objective‑C版本的Realm,或者构建Objective‑C与Swift混合的APP,那么请使用Realm的Objective‑C版本。 注意:不能同时使用Objective‑C版本和Swift版本的 Realm。

从这里开始

Download Realm Swift or see the source on GitHub.

准备工作

  • 使用 Realm 构建 Apps 的基本要求:iOS >= 8, OS X >= 10.9 & WatchKit.
  • 需要Xcode >= 6.3.
  • 现在Realm Swift是基于Swift 1.2设计的。如果你想使用Swift 2.0也是可以的, 但是直到苹果在年末正式发布新版语言前,相应支持的软件版本需要你手动安装

安装 (Swift 1.2)

  1. 下载Realm的最新版本并解压压缩包。
  2. 前往你 Xcode 工程的”General”设置页。从ios/ 或者osx/文件夹中,拖拽RealmSwift.frameworkRealm.framework到”Embedded Binaries”选项中,确认Copy items if needed被选中并点击Finish
  3. 在你的测试目标的”Build Settings”中,添加RealmSwift.framework的上级目录到你的”Framework Search Paths”中。
  4. 如果你打算在你的iOS工程中使用 Realm,请在你的 app 目标的“Build Phases”中创建一个新的”Run Script Phase”,并在文本框中写入 bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh" 因为要绕过这个APP商店提交的bug,这一步在打包二进制发布版本时是必须的。

注意: 动态frameworks在OS X 或者 iOS 8和以上版本中被支持。

  1. 安装CocoaPods 0.37.1 或者更高版本([sudo] gem install cocoapods)。
  2. 在你的Podfile中,添加use_frameworks!pod 'RealmSwift' 到你主要和测试目标。
  3. 在终端运行pod install
  4. 采用 CocoaPods 生成的.xcworkspace来运行工程!

点击CocoaPods网站来查询相关内容。

  1. 添加github "realm/realm-cocoa"到你的Cartfile。
  2. 运行carthage update.
  3. iOS:Carthage/Build/iOS/文件夹中拖拽RealmSwift.frameworkRealm.framework 到你 Xcode 工程”General”的”Linked Frameworks and Libraries”选项卡中。 OS X:Carthage/Build/Mac/文件夹中拖拽RealmSwift.frameworkRealm.framework到你 Xcode 工程”General”的”Embedded Binaries”选项卡中。
  4. iOS: 在你APP targets的“Build Phases”设置选项卡,点击“+”图标并选择“New Run Script Phase”。在新建的Run Script中,填写:

    /usr/local/bin/carthage copy-frameworks

    在“Input Files”内添加frameworks路径,例如:

    $(SRCROOT)/Carthage/Build/iOS/Realm.framework
    $(SRCROOT)/Carthage/Build/iOS/RealmSwift.framework

    因为要绕过APP商店提交的bug,这一步在打包二进制发布版本时是必须的。

点击Carthage工程查看更多内容。

安装 (Swift 2.0)

Realm有一个基于Swift 2.0的长期支持版本long-running branch,它将被持续更新,直到苹果正式发布 swfit2.0 语言。

  1. 克隆GitHub上的Realm cocoa并切换到swift-2.0分支。
  2. 在克隆的目录下,运行sh build.sh build
  3. 从工程中移除 RealmSwift.frameworkRealm.framework 已经存在的二进制文件。
  4. 前往Xcode的”General”设置界面。从build/ios/swift/或者osx/文件夹中拖拽RealmSwift.frameworkRealm.framework文件到”Embedded Binaries”选项中。选中Copy items if needed并点击Finish
  5. 在你的测试target的”Build Settings”中,添加RealmSwift.framework 的目录到”Framework Search Paths”选项卡中。

在你的Podfile中添加如下段落到你的主要和测试target中. ruby # 你需要添加“Realm”和“RealmSwift” use_frameworks! pod 'Realm', :git => 'https://github.com/realm/realm-cocoa.git', :branch => 'swift-2.0' pod 'RealmSwift', :git => 'https://github.com/realm/realm-cocoa.git', :branch => 'swift-2.0'

因为 Realm Swift 需要基于预编译文件Carthage不能指定scheme进行编译,所以Carthage还没有有提供Swift 2.0版本。

Realm浏览器/数据库管理器

我们也提供一个独立Mac app 对.realm数据库进行读取和编辑。它位于browser/下的release zip 中。

你可以使用菜单中的Tools(工具) > Generate demo database(生成演示数据库)中加载一个有样本数据的测试数据库。

如果你需要寻找你程序的Realm文件,请查看StackOverflow上的这个答案

你可以从Mac App Stroe安装Realm Browser。

Xcode插件

我们的Xcode插件使新建 Realm 模型更加方便。

安装 Realm 插件的最简单方式是通过点击”RealmPlugin”文件夹下的Alcatraz。你也可以手动进行安装:打开release zip 中的plugin/RealmPlugin.xcodeproj并进行编译。重启 Xcode后生效。如果你使用 Xcode 菜单来建立一个新文件(File > New > File… — or ⌘N) ,你就可以看到有一个新建Realm模型(create a new realm model)的选项。

API手册

你能查询我们的完整版API手册,里面包含所有类和方法等信息。

示例

你可以在examples/目录release zip 下查看iOS和OS X版本的示例。它们演示了Realm得很多功能和特性,例如数据库迁移,如何与UITableViewController’s一起使用,加密等等。

获得帮助

  • Bug和功能的重现可以直接在GitHub repo提交给我们。
  • 讨论和支持: [email protected]
  • StackOverflow: 查找之前的有#realm标签的问答, — 或者,开一个新的
  • 注册登陆,订阅我们定期发布的community newsletter。里面有一些非常有用的提示和其他的用例。当有新的Realm博客或者教程出现,邮件也会通知你。

数据模型(model)

Realm数据模型是基于传统特征类Swift来进行定义的。 可通过简单的Object的子类或者一个已经存在的模型类,你就能轻松创建一个Realm的数据模型对象(data model object)。 Realm模型类函数与其他Swift对象相同 - 你可以给它们添加你自己的方法(method)和协议(protocol)然后和其他的对象一样使用。 唯一的限制就是从它们被创建开始,只能在一个进程中被使用。

你只需要为对象列表添加目标类型的属性(property)或者List的,就可以创建数据库关联和嵌套数据结构。

import RealmSwift

//狗的模型
class Dog: Object {
    dynamic var name = ""
    dynamic var owner: Person? // 可以设置可选型
}

//人的模型
class Person: Object {
    dynamic var name = ""
    dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
    let dogs = List<Dog>()
}

所有模型在代码启动时就已经被定义,所以即使全程没有调用,它们也会被赋值。

通过Object 可查看更多细节。

属性(property)种类

Realm支持以下的属性(property)种类:Bool, Int8, Int16, Int32, Int64, Double, Float, String, NSDate 精度到秒, 和NSData.

你可以使用List<Object>Object模拟对一或对多的关系——Realm也支持Object继承。

属性(property)特性(attributes)

Realm 模型属性需要设置为var dynamic以便于访问数据库底层数据。当然这也包含一个例外,List不能被设为动态属性,因为通用属性不能被转化为Objective‑C运行。

数据模型定制

许多类的方法都支持深度定制:

  • Object.indexedProperties()可以通过重写来设定特定属性(property)的属性值(attrbutes)。例如某个属性值要添加索引。 ```swift class Book: Object { dynamic var price = 0 dynamic var title = “”

    override static func indexedProperties() -> [String] { return [“title”] } } ```

  • Object.primaryKey() 能通过重载来设置模型主键。通过声明主键,用户可以查询数据并可高效更新。程序会强制要求主键具有唯一性。

class Person: Object {
  dynamic var id = 0
  dynamic var name = ""

  override static func primaryKey() -> String? {
    return "id"
  }
}
class Person: Object {
  dynamic var tmpID = 0
  var name: String { // 计算属性被自动忽略
    return "\(firstName) \(lastName)"
  }
  dynamic var firstName = ""
  dynamic var lastName = ""

  override static func ignoredProperties() -> [String] {
    return ["tmpID"]
  }
}

存储对象

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

Rrealm的对象可以被实例化并且被单独使用,和其他常规Swift对象无异。 如果你想要在多个线程中共享或者永久保存以重复使用对象,你必须将其存储到Realm数据库中——这个操作必须在写事务中完成。

添加对象

向 Realm 中添加对象的步骤如下:

// 创建一个人(PErson)对象
let author = Person()
author.name = "David Foster Wallace"

// 获取默认的 Realm
let realm = Realm()
// You only need to do this once (per thread)

// 通过处理添加数据到 Realm 中
realm.write {
  realm.add(author)
}

等到你把这个对象添加到realm数据库里面之后, 你可以在多个线程里面共享他们。并且从现在开始,你所做的每一次更改(必须在一个写事务中完成)也会被永久储存。等到写事务完成,这个更改将对所有共享这个Realm数据库的线程可见。

这类似其他永久性解决方案,我们建议你在一个独立的进程中进行写入。

因为 Realm 采用的是 MVCC 框架,读取并 不会 因为一个进行中的写事务而受到影响。除非你需要多个线程同时写入数据,不然你应该采用批量化的写入模式而不是多次少量的写入方式。

查看RealmObject来获得更多内容。

更新数据

我们可以通过写入来更新对象数据。

// 更新数据对象
realm.write {
  author.name = "Thomas Pynchon"
}

如果你为你的模型设置了主键,你可以使用Realm().add(_:update:)来更新对象或者在对象不存在时插入新的对象。

// 创建一个已存在的主键的 book 对象。
let cheeseBook = Book()
cheeseBook.title = "Cheese recipes"
cheeseBook.price = 9000
cheeseBook.id = 1

// 更新 book 的 id 为1
realm.write {
  realm.add(cheeseBook, update: true)
}

如果 book 的主键(id)的值不为1,那么系统会新建一个 book 对象插入数据库。

删除对象

通过写入处理的Realm().delete(_:),我们能够删除数据库中的对象。

let cheeseBook = ... // 存储在 Realm 的 Book

// 删除对象
realm.write {
  realm.delete(cheeseBook)
}

你也能删除 Realm 下的所有对象。注意,在删除数据后,Realm文件的大小不会改变。通过保留空间以便于日后快速存储数据。

// 删除 realm 下的所有数据
realm.write {
  realm.deleteAll()
}

查询

通过查询,系统会返回包含一连串对象的Results 。Results 的接口与Array十分相似,存储在Results 的对象可通过序号进行索引。不像Array的是,Results一样需要被赋予类型且存储的对象属于一个子类类型。

所有的查询(包括查询和熟性访问)都是在 Realm 可用的。数据访问时,数据仅能进行读取。

数据的查询结果不是对你的数据进行拷贝:修改查询结果(如使用写入操作)将直接改动硬盘上的数据。同样,你能直接从包含于Results的Objects中遍历所有映射

通过类型检索对象

从 Realm 中检索对象的最基本方法是Realm().objects(_:)。 它将返回 Realm 中满足子类的所有结果。

// 查询 Realm
let dogs = Realm(path: "pets.realm").objects(Dog)

谓词/条件查询

如果你熟悉NSPredicate,那么你就很容易掌握 Realm 的查询方法。Objects, Realm, List, and Results 所有的查询方法都允许使用简单的NSPredicate语句,你只需要传递相应地NSPredicate实例,谓词字符串,谓词格式字符串,就可以获取你想要的NSArray实例啦。就和NSArray一样的。

例如,下面的代码就展示了如何调用Results().filter(_:...)来在 Realm 数据库中的所有颜色是黄褐色的,名字开头是“B”的狗的实例: ```swift let realm = Realm() // 使用条件字符串查询 var tanDogs = realm.objects(Dog).filter(“color = ‘tan’ AND name BEGINSWITH ‘B’”)

// 使用NSPredicate查询 let predicate = NSPredicate(format: “color = %@ AND name BEGINSWITH %@”, “tan”, “B”) tanDogs = realm.objects(Dog).filter(predicate) ```

查看苹果的谓词编程指导手册来获取更多关于使用谓词/条件查询和NSPredicate Cheatsheet的信息。 Realm支持许多常见谓词/条件:

  • 在比较中, 操作数可以是属性名或者常量。但是其中至少有一个是属性名。
  • 比较操作符==, <=, <, >=, >, !=, and BETWEEN支持int, long, long long, float, double, and NSDate。例如age == 45
  • ==, !=, e.g. Results<Employee>().filter("company == %@", company)
  • 布尔属性支持 == and !=
  • 对于NSString和NSData, 我们支持的操作符有 ==, !=, BEGINSWITH, CONTAINS, 和 ENDSWITH operators,例如name CONTAINS ‘Ja’
  • string支持模糊查询, 例如name CONTAINS[c] ‘Ja’。注意其中字符大小写不做区分。
  • Realm 还支持如下的复合型操作符: “AND”, “OR”, and “NOT”。例如 name BEGINSWITH ‘J’ AND age >= 32
  • 包含操作符 IN 例如 name IN {‘Lisa’, ‘Spike’, ‘Hachi’}

  • ==, !=支持与 Nil 比较,例如 Results<Company>().filter("ceo == nil")。注意这只用于有映射关系的对象,例如,CEO 是公司模型的属性。
  • ANY比较, 例如ANY student.age < 21
  • 注意,我们虽然不支持复合表达式(aggregate expression type),但是我们支持BETWEEN操作符,例如:Results<Person>.filter("age BETWEEN %@", [42, 43]])

详情请点击Results().filter(_:...)

条件排序

Results 允许你指定一个排序要求并且根据一个或多个属性进行排序。例如,下列代码调用排序功能并把结果按狗的名称升序排列:

// 通过名称查询名称首字母为“B”的狗
let sortedDogs = Realm().objects(Dog).filter("color = 'tan' AND name BEGINSWITH 'B'").sorted("name")

更多信息请查询Results().filter(_:...)Results().sorted(_:ascending:)

###链式查询

Realm查询引擎的一个独特属性就是它能够进行简单快捷的链式查询,而不需要像传统数据库一样的麻烦。

例如,如果你要所有黄褐色的小狗的结果序列,然后从中查找名字开头是“B“的小狗。 你可以通过以下两个操作的来完成链式查询:

let tanDogs = Realm().objects(Dog).filter("color = 'tan'")
let tanDogsWithBNames = tanDogs.filter("name BEGINSWITH 'B'")

Realm数据库

默认的realm数据库

你可能已经发现我们总是通过Realm()来对 Realm 进行初始化。这个方法会返回RLMRealm对象,并指向到你 app 的Documents文件夹下的“default.realm”文件。

你可以使用Realm.defaultPath来自定义默认 realm 数据库。这步操作常用于来测试 app 或者在iOS 8 Shared Container的多个 app 间共享数据库。

其他的realm数据库

有的时候,在不同位置拥有多个分离的数据库是十分有用的。例如,如果你需要将数据绑定到一个App中,你需要打开一个只读的 Realm。点击Realm(path:) and Realm(path:encryptionKey:error:)来查询更多信息。

请注意初始化 Realm 的第三方路径需要有写入权限。常用存储 Realm 的文件夹是iOS的“Documents”和OSX的“Application Support”。具体情况,请遵循Apple’s iOS Data Storage Guidelines, 它推荐将文件存储在<Application_Home>/Library/Caches目录下。

内存数据库

通常情况下,Realm 数据库是存储在硬盘中的,但是你能够使用Realm(inMemoryIdentifier:)创建一个纯内存数据库:

let realm = Realm(inMemoryIdentifier: "MyInMemoryRealm")

内存数据库在每次程序启动时不会保存数据。但是,这不会妨碍到realm的其他功能,包括请求,关系和线程安全。假如你需要灵活的数据读写但又不想永久储存,那么纯内存数据库对你来说一定是一个不错的选择。

注意: 如果某个纯内存Realm实例没有被引用,所有的数据就会被释放。强烈建议你在app的生命周期内保持对Realm内存数据库的强引用以避免不期望的数据丢失。

跨线程使用数据库

在不同的线程间使用同一个Realm数据库,你需要为 app 的不同线程初始化相应的Realm。只要他们路径是一样的,那么他们就会调用相同的文件。

我们还_不支持_跨线程共享Realm 相同 realm 文件的初始化需要有相同的readOnly 属性(或者readwrite或者readonly).

在一个独立流程中,一次性写入大量数据是一个高效的方式。它可能使用Grand Central Dispatch在后台完成写入操作。RLMRealm对象使用线程并不安全,也不能在线程中共享数据,你必须在你想读取或者写入的每个thread/dispatch_queue中获取一个 Realm 实例。下面例子展示如何在后台插入一百万个数据:

dispatch_async(queue) {
  autoreleasepool {
    // 在这个线程中获取realm和table
    let realm = Realm()

    // 先开启写入操作,然后将写入操作分成多个微小操作
    for idx1 in 0..<1000 {
      realm.beginWrite()

      // 通过字典插入排数据,其中属性属性忽略不记
      for idx2 in 0..<1000 {
        realm.create(Person.self, value: [
          "name": "\(idx1)",
          "birthdate": NSDate(timeIntervalSince1970: idx2)
        ])
      }

      // 关闭写入流程来确保数据在其他线程可用
      realm.commitWrite()
    }
  }
}

在Realm数据库间拷贝数据

拷贝 Realm 对象到另一个 Realm 数据库十分简单,只需粘贴原始对象到Realm().create(_:value:update:)。例如, Realm().create(MyObjectSubclass.self, value: originalObjectInstance).

查询 Realm 文件

如果你需要寻找 app的 Realm 文件,那么请查看StackOverflow答案 for detailed instructions.

在 app 中建立 Realm 数据库

为了能够使你的用户在第一次启动时就能够直接使用,一种通常的做法就是为 app 加载初始化数据。具体步骤是:

  1. 第一步定位 realm。你应该使用与最终发售版相同的数据模型来创建 realm 并绑定数据到你的 app 中。因为 realm 文件是跨平台,所以你能够在模拟器中测试你的OS X app (查看你的JSONImport example)或者你的 iOS app。
  2. 在加载 realm 文件的代码处,你需要在程序最后对文件进行拷贝(就是Realm().writeCopyToPath(_:encryptionKey:))。这有助于减少数据库体积,使你发布的 app 体积更小。
  3. 拖拽你的最终的 realm 文件的拷贝到你的最终 app 的Xcode工程导航栏中。
  4. 前往你的 appp 的Xcode编译选项卡,添加 realm 文件到”Copy Bundle Resources”。
  5. 这样你就能够在你的 app 中更加便捷的使用数据库。你能通过使用NSBundle.mainBundle().pathForResource(_:ofType:)来得到数据库路径。
  6. 你也能通过调用Realm(path:readOnly:encryptionKey:error:)来创建一个只读 realm。或者,你想基于初始化数据来创建一个可写入的 realm 文件,那么你可以使用NSFileManager.defaultManager().copyItemAtPath(_:toPath:error:) 把绑定文件拷贝到 app 的 Documents 文件夹下。然后使用Realm(path:)来构建 realm 数据库。

你能够参考我们的迁移例程app来学习如何使用绑定的 realm 文件。

映射关系

通过使用Object和List来连接各个Object。List与Array有详细的接口,包含在List 的对象能通过索引进行访问。不像Array,List需要赋予属性,并且要保证加载的Object有这单一的属性。更多信息请查看List

假如说你预先定义了一个”人“模型 (如下),我们再来创建一个”狗“模型:

class Dog: Object {
    dynamic var name = ""
}

对一关系

对于多对一和一对一关系, 仅仅只需要定义一个Object 子类类型的属性:

class Dog: Object {
    ... // 其他属性声明
    dynamic var owner: Person?
}

你可以像往常一样使用这个属性:

let jim = Person()
let rex = Dog()
rex.owner = jim

当你使用Object属性, ,你可以通过通常的属性语法获取嵌套属性。例如rex.owner.address.country会遍历这个所有对象,来从 Realm 获得每一个你想要的对象。

对多关系

你可以通过使用List属性来定义一个对多关系。List包含多个其他单一类型的Object,并且拥有一个和a mutable Array非常相似的接口。

为了向已经包含多个“狗“的”人”模型添加一个“狗”属性,我们必须定义一个List<Dog>类型:

class Person: Object {
    ... // 其他属性声明
    let dogs = List<Dog>()
}

你可以可以像往常一样对 List进行读取和赋值:

let someDogs = Realm().objects(Dog).filter("name contains 'Fido'")
jim.dogs.extend(someDogs)
jim.dogs.append(rex)

反向关系

通过反向关系(又称 backlink),你可以通过一个特定的属性获得所有链接到一个给定对象的对象。例如,通过对‘狗’调用Object().linkingObjects(_:forProperty:)就能够返回所有连接到调用目标的特性类型的对象:

class Dog: Object {
    dynamic var name = ""
    dynamic var age = 0
    var owners: [Person] {
        // Realm doesn't persist this property because it only has a getter defined
        // 通过 Person.dogs的逆向关系来定义“主人”
        return linkingObjects(Person.self, forProperty: "dogs")
    }
}

通知

每当一次写事务完成Realm实例都会向其他线程上的实例发出通知,可以通过注册一个block来响应通知:

// 监视Realm通知
let token = realm.addNotificationBlock { notification, realm in
    viewController.updateUI()
}

只要有任何的引用指向这个返回的notification token,它就会保持激活状态。在这个注册更新的类里,你需要有一个强引用来钳制这个token, 因为一旦notification token被释放,通知也会自动解除注册。

点击Realm().addNotificationBlock(_:) and Realm().removeNotification(_:) 查看更多信息。

迁移

当你使用任意一个数据库时,你都可能希望修改你的数据模型。因为Realm 的数据模型有着标准Swift的接口,使得修改模型就像修改其他Swift接口一样方便。例如,假设我们有如下‘人’模型:

class Person: Object {
    dynamic var firstName = ""
    dynamic var lastName = ""
    dynamic var age = 0
}

假如我们想要更新数据模型来添加一个“全名”(fullname)属性, 而不是使用过去的分开的“姓”+“名”属性,为此我们只需要改变对象的接口,范例如下:

class Person: Object {
    dynamic var fullName = ""
    dynamic var age = 0
}

在这个时候如果你保存了数据,那么Realm就会注意到代码和硬盘数据不匹配。每当这时,你必须对数据构架进行迁移,否则就会抛出错误。

进行迁移

通过调用setSchemaVersion(_:_:_:)可以进行自定义数据迁移以及构架相应版本。数据迁移模块将会为你提供相应地逻辑操作来用于更新数据构架。调用setSchemaVersion(_:_:_:)之后, 任何需要迁移的Realm数据库都会自动使用指定的迁移模块并且更新到相应地版本。

例如,假设我们想要把之前‘人’的子类迁移,如下所示是最简化的数据迁移流程:

// 插入到[ application(application:didFinishLaunchingWithOptions:)

// Notice setSchemaVersion设置为1,这通常需要手动设置。它必须比之前版本的高,否则就会抛除一个RLMException
setSchemaVersion(1, Realm.defaultPath, { migration, oldSchemaVersion in
  // 因为我们没有进行过数据迁移,所以oldSchemaVersion == 0
  if oldSchemaVersion < 1 {
    // 什么也不要做,Realm会自动监测新属性和就属性,并自动完成更新。
  }
})
// 现在能调用`setSchemaVersion(_:_:_:)`来打开老数据。
// Realm 自动完成数据更新,并成功打开数据库。

对于版本更新,人们所需要做的就是留出一个空模块,Realm通过这个最简短的程序可以自动完成更新。

虽然这是系统能接受的最简化的迁移,我们应当用有意义的代码来添加新的属性(这里指“fullname”)。在数据迁移模块中,我们可以调用Migration().enumerate(_:_:)来列举Realm中每一特定文件,并执行必要的迁移判定。 注意遍历Object需要使用 oldObject ,而更新是使用newObject

// 插入到你的 application(application:didFinishLaunchingWithOptions:)

setSchemaVersion(1, Realm.defaultPath, { migration, oldSchemaVersion in
  if oldSchemaVersion < 1 {
    // 迭代方法enumerate(_:_:)
    // 遍历 Realm 文件中的每一个'Person'对象
    migration.enumerate(Person.className()) { oldObject, newObject in
      // 将字母合并到一个字符串中
      let firstName = oldObject!["firstName"] as! String
      let lastName = oldObject!["lastName"] as! String
      newObject!["fullName"] = "\(firstName) \(lastName)"
    }
  }
})

一旦迁移成功结束,Realm和其所有文件都可被你的app正常存取。

添加更多的版本

假如说现在我们有两个的之前版本:

// v0
class Person: Object {
    dynamic var firstName = ""
    dynamic var firstName = ""
    dynamic var age = 0
}

// v1
class Person: Object {
    dynamic var fullName = "" //新属性
    dynamic var age = 0
}

// v2
class Person: Object {
    dynamic var fullName = ""
    dynamic var email = "" //新属性
    dynamic var age = 0
}

我们的迁移模块里面的逻辑大致如下:

setSchemaVersion(2, Realm.defaultPath, { migration, oldSchemaVersion in
    // 迭代方法enumerateObjects:block
    // 遍历 Realm 文件中的每一个'Person'对象
  migration.enumerate(Person.className()) { oldObject, newObject in
    // 添加'fullName'属性到版本0中
    if oldSchemaVersion < 1 {
      let firstName = oldObject!["firstName"] as! String
      let lastName = oldObject!["lastName"] as! String
      newObject!["fullName"] = "\(firstName) \(lastName)"
    }

    // 添加'email'属性到版本0或版本1中
    if oldSchemaVersion < 2 {
        newObject!["email"] = ""
    }
  }
})

// Realm 自动完成数据更新,并成功打开数据库。
let realm = Realm()

想要了解更多关于数据库迁移的信息,请点击迁移例程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。

加密

Please take note of the Export Compliance section of our LICENSE, as it places restrictions against the usage of Realm if you are located in countries with an export restriction or embargo from the United States.

在iOS平台,通过调用NSFileProtection的 API 可以使用很少的资源就能完成 Realm 的文件加密。如果这样做要注意两点:1)这种Realm文件不能跨平台(因为NSFileProtection只有 iOS 有),2)Realm文件不能在没有密码保护的 iOS 设备中进行编码。为了避免这些(或者你想构建一个 OS X 的 app),你需要使用Realm级别的加密。

Realm支持在创建 Realm 数据库时采用64byte 的密钥对数据文件进行 AES-256+SHA2 加密。

// 产生随机加密密钥
let key = NSMutableData(length: 64)!
SecRandomCopyBytes(kSecRandomDefault, UInt(key.length),
    UnsafeMutablePointer<UInt8>(key.mutableBytes))

// 打开加密文件
var error: NSError?
let realm = Realm(path: Realm.defaultPath,
    readOnly: false, encryptionKey: key, readOnly: false, error: &error)
if realm == nil {
    // 如果密钥错误,`error` 会提示an invalid database
    println("Error opening realm: \(error)")
    return
}

// 像正常情况一样使用 Realm
let dogs = realm.objects(Dog).filter("name contains 'Fido'")

这样硬盘上的数据都能都采用AES-256来进行加密和解密,并用 SHA-2 HMAC 来进行核实。每次你要获得一个 Realm 实例时,你都需要提供一次密钥。Realm 也会在内存中存储你的密钥,这样再打开相同路径下的 Realm ,密钥就会被自动调用。例如,为默认 Realm 设置密钥(便捷方式):

// 产生随机加密密钥
let key = NSMutableData(length: 64)!
SecRandomCopyBytes(kSecRandomDefault, UInt(key.length),
    UnsafeMutablePointer<UInt8>(key.mutableBytes))

// 为默认 Realm 设置默认密钥
Realm.setEncryptionKey(key, forPath: Realm.defaultPath)

// 像正常情况一样使用 Realm
let dogs = realm.objects(Dog).filter("name contains 'Fido'")

加密例程app展示了一个产生密钥,安全存储和解密的 app 例程。

加密 Realm 只占用很少的资源(通常只会慢10%)。

请注意,如果你使用第三方的崩溃记录工具(crashlytics,plcrashreporter等)应在打开加密 Realm 前完成注册,不然你得到的数据将都是错误的。

调试

使用 Realm 的 Swift API 进行调试需要在 LLDB 中进行。

注意尽管在我们Xcode插件中装有 LLDB 脚本允许你在 Xcode 的 UI 视图中查看你 Realm 变量的内容,但是这对于 Swift 无效,他们显示的数据都是错误的。你需要再 LLDB 中通过‘po’来查看在 Realm 中的数值。

调试加密的Realm

附带的 LLDB 可用于查看加密的 Realm现在还不被支持。当你需要调试时,你可以在你的环境中设置REALM_DISABLE_ENCRYPTION=YES来禁用加密,这可以将加密的 Realm 转化为一个未加密的 Realm。

测试

测试Realm App

重载默认 Realm 的路径

最简单的使用和测试 Realm app 的方法就是使用默认realm。为了避免在测试中重载 app 数据,需要在测试中设置为默认路径(Realm.defaultPath = "/path/to/file.realm")。

Realm 注入

另一种测试 realm 相关代码的方式是加载你想测试Realm的所有方法。这样你就能在运行 app 和测试 app 时,查看这些方法是否可行。例如,假如你 app 使用了一种通过 JSON API 来获得用户的配置文件的方法,那么你肯定想测试本地配置文件是否被正确的创建:

//  APP 编码
static func updateUserFromServer() {
  let url = NSURL(string: "http://myapi.example.com/user")
  NSURLSession.sharedSession().dataTaskWithURL(url) { data, _, _ in
    self.createOrUpdateUserInRealm(Realm(), withData: data)
  }
}

public static func createOrUpdateUserInRealm(realm: Realm, withData data: NSData) {
  let object: [String: String] =
          NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
  realm.write {
    realm.create(object, update: true)
  }
}

// 测试编码
func testThatUserIsUpdatedFromServer() {
  let testRealm = Realm(path: kTestRealmPath)
  let jsonString: NSString = "{\"email\": \"[email protected]\"}"
  let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)
  ClassBeingTested.createOrUpdateUserInRealm(testRealm, withData: jsonData)
  let expectedUser = User()
  expectedUser.email = "[email protected]"
  XCTAssertEqual(realm.objects(User).first!,
                 expectedUser,
                 "User was not properly updated from server.")
}

避免在测试目标中连接 Realm 和你的测试代码

因为 Realm 是一个动态框架,你需要确认你的测试单元能否正确的找到 Realm。你需要去添加RealmSwift.framework的上级目录到你的测试单元的”Framework Search Paths”中。

如果你的测试失败,并提示"Object type '...' not persisted in Realm",这很有可能是因为直接将你的测试目标连接到你的 Realm 框架。你应该从你测试目标中移除 Realm。

你也应该确保你模型类文件只连接到你的应用或框架目标中,而不是你的测试目标中。否则,这些类将会在测试时被复制,这将为调试带来麻烦(see https://github.com/realm/realm-cocoa/issues/1350 for details)。

你也要保证所有的测试代码在你测试目标中(对于 Swift 需要使用‘public’ ACL)

重置状态

使测试单元彼此隔离是十分重要的。出于这个目的,我们建议你在硬盘删除 realm 文件并在每次测试前重置 Realm 的内部状态。这可以在使用 XCTest 时,调用setUptearDown来完成:

// 帮助

let realmPathForTesting = ""

func deleteRealmFilesAtPath(path: String) {
  let fileManager = NSFileManager.defaultManager()
  fileManager.removeItemAtPath(path, error: nil)
  let lockPath = path + ".lock"
  fileManager.removeItemAtPath(lockPath, error: nil)
}

// 在XCTestCase子类里:

override func setUp() {
  super.setUp()
  deleteRealmFilesAtPath(realmPathForTesting)
  Realm.defaultPath = realmPathForTesting
}

override func tearDown() {
  super.tearDown()
  deleteRealmFilesAtPath(realmPathForTesting)
}

测试 Realm

Realm有一套完整的测试套件,它能测试每一条语句,所以你的 app 的测试不应只测试 Realm 本身,还要测试 Realm 的使用情况。 因为 Realm 是开源的,我们很乐意接受绑定、测试和文档相关的帮助。

REST APIs

Realm轻松的整合了REST API, 这使得Realm在几个方面胜过了无本地缓存的REST API:

  • Realm缓存数据使得你能提供离线体验,普通的REST API无法做到这一点,他们通常一定需要网络连接.
  • 通过缓存整个数据集,你能实现本地查询,这将比只使用 REST 有更好的使用体验。
  • 减轻服务器端负荷,只需要在更新和修改数据时进行必要的访问。

最佳操作

  1. 异步请求 — 网络请求和其他一些操作应该放到后台,以免影响交互效果。同理Realm数据库中大规模插入和修改应该在后台进行。你可以用通知来相应后台操作。
  2. 缓存大数据库 — 我们建议你对可能使用的数据进行预处理并且存储到Realm中。 这么做可以让你在本地数据集中进行查询。
  3. 插入或更新 — 如果你的数据集有一个特有的标识符, 例如一个主键, Realm().add(_:update:):如果你从API得到响应, 这个method会从Realm中查询这个响应是否有记录。 如果在本地有记录, 就可以从响应中根据最新的数据进行更新。如果没有,就将该响应插入到Realm数据库中。

范例

以下是一个如何应用一个使用了REST API的Realm的示例。在这个示例里,我们将从foursquare API里获取一组JSON格式的数据,然后将它以Realm Objects的形式储存到默认realm数据库里。

如果你想参考类似示例的实际操作,请观看video demo

首先我们要创建一个默认Realm数据库的实例,用于存储数据以及从 API 获取数据。为了更简单易读,我们在这个例子里面运动了NSData(contentsOfURL:)

// 调用API
let url = NSURL(string: "https://api.foursquare.com/v2/venues/search?near=San%20Francisco&limit=50")!
let response = NSData(contentsOfURL: url)!

// 反序列化JSON响应
let json = (NSJSONSerialization.JSONObjectWithData(response,
    options: NSJSONReadingOptions(0),
      error: nil) as! NSDictionary)["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然后将其属性通过插入功能手动映射到一个 Object 上。为了演示效果,在这个示例里,我们将直接把 NSDictionary插入到Realm中,然后让Realm自动快速将其属性映射到Objects上。为了确保示例能够成功,我们需要一个属性完全匹配JSON数据特点的Objects框架。JSON数据特点如果不匹配Objects,数据将在植入时自动被忽略。 以下Objects的定义是有效的:

class Contact: Object {
    dynamic var phone = ""

    static func primaryKey() -> String? {
        return "phone"
    }
}

class Location: Object {
    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: Object {
    dynamic var id = ""
    dynamic var name = ""
    dynamic var contact = Contact()
    dynamic var location = Location()

    static func primaryKey() -> String? {
        return "id"
    }
}

因为结果集是以数组的形式给我们的,我们要调用Realm().create(Venue.self, value: value)来为每个元素创建一个对象. 这里会创建 Venue 和一个JSON格式的子对象,并将这些新建的对象加入到默认realm数据库中:

//从响应中提取venues数组
let venues = json["venues"] as! [NSDictionary]

let realm = Realm()
realm.write {
  // 为数组中的每个元素保存一个对象(和依赖)
  for venue in venues {
    realm.create(Venue.self, value: venue, update: true)
  }
}

下一步

你可以看一下我们给出的示例 看看在app中应该如何使用realm。(我们已经有越来越多的样本了!) 做一个愉快的开发者!你也总是可以在realm-cocoa上实时的和其他开发者聊天。

当前局限

Realm现在还是beta版本。我们还在为1.0的发布一直不断地添加新特性,修复bug。我们整理了一些普遍存在的限制

如果你想要看到完整地issue列表,参见 GitHub issues for a more comprehensive list of known issues.

通用限制

Realm 致力于在灵活性和性能之间做取舍。为了实现这个目标,在一个Realm中存储信息各个方面都有通用限制。例如:

  1. 类名称必须在长度为0和63字节之间。支持utf8字符。如果超出了这个限制,应用程序的初始化将引发异常。
  2. 属性名称必须在长度为0和63字节之间。支持utf8字符。如果超出了这个限制,应用程序的初始化将引发异常。
  3. NSData保存的数据应不大于16MB。存储大量的数据,可通过将其分解为16MB的块或存储直接在文件系统,需要将路径存储在 realm 中。如果你的应用程序试图存储一个大于16MB的单一文件,系统将抛除异常。
  4. NSDate存储精度为秒。它不能使用+[NSDate distantFuture] or +[NSDate distantPast]。参考NSDate在现实的局限可查看更多相关信息。
  5. 任何一个 Realm 文件也不能大于 iOS app 允许的内存空间总量。这个值会根据不同设备和当时的内存使用情况有所不同(这里又一个关于相关方面的讨论: rdar://17119975)如果你想存储更大的数据,请将数据映射到多个文件中。

暂时不支持细粒化通知

虽然要在realm发生变化的时候可以接到通知 (参见 通知), 但现在我们还不能从notification里面得知什么东西被添加/删减/移动/更新了。 我们会尽快完善这个功能的。

NSDate精度到秒

一个包含非整秒数的NSDate在存入realm的时候,会在秒的地方被截断。我们正在修复这个问题。 可参考 GitHub issue #875。同时,你可以无损存储NSTimeInterval格式。

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

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

不支持 KVO

Realm不支持KVO, 但它有自己的通知机制(Notifications)。我们已经添加了 KVO 支持: 详看GitHub issue #601.

文件大小&版本跟踪

一般来说 Realm 数据库比 SQLite 数据库在硬盘上占用更少的空间。如果你的 Realm 文件大小超出了你的设想,这可能是因为你Realm中包含旧版本数据。

为了使你的数据有相同的显示方式,Realm 只在循环迭代的开始时更新数据版本。这意味着,如果你从 Realm 读取了一些数据并进行了长期操作,而在其他线程进行写的操作,版本将不被更新,Realm 将保存中间版本的数据,但是这些数据已经没有用了,这导致了文件大小的增长。这部分空间会在下次写入操作时被重复利用。这些操作可以通过调用 Realm().writeCopyToPath(_:encryptionKey:)来实现。

为了避免这个问题,你可以调用invalidate,来告诉 Realm 你不再需要那些拷贝到 Realm 的数据了。这可以使我们不必跟踪这些对象的中间版本。在下次出现新版本时,再进行版本更新。

你可能在 Realm 使用Grand Central Dispatch时也发现了这个问题。在 dispatch 结束后自动释放调度队列(dispatch queue)时,调度队列(dispatch queue)没有随着程序释放。这造成了直到Realm对象被释放后,Realm 中间版本的数据空间才会被再利用。为了避免这个问题,你应该在 dispatch 队列中,使用一个显式的自动调度队列(dispatch queue)。

不可以从Objective‑C中获得属性列表

如果你打算使用 Realm Swift 从Objective‑C加载数据,你需要将你的List属性设为privateinternal。这是因为一个已知的Swift bug,自动生成的 Objective‑C头文件(-Swift.h)不全。具体请查看GitHub issue #1925

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的彻底免费的, 哪怕你用于商业软件。

你们计划怎么赚钱?

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

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

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