Realm Swift

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:http://www.javashuo.com/article/p-vogrlxpm-bz.html 
➤若是连接不是山青咏芝的博客园地址,则多是爬取做者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持做者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★html

先决条件

  • XCode 9.2或更高版本
  • iOS 8或更高版本的目标,macOS 10.9或更高版本,或任何版本的tvOS或watchOS

安装

  1. 安装CocoaPods 1.1.0或更高版本。
  2. 运行pod repo update以使CocoaPods了解最新的Realm版本。
  3. 在你Podfile,添加use_frameworks!pod 'RealmSwift'你的主和测试目标。
  4. 从命令行运行pod install
  5. 使用.xcworkspaceCocoaPods生成文件来处理您的项目!

入门

若是您但愿使用Objective-C中的 Realm ,或者使用混合的Objective-C和Swift应用程序,请参阅Realm Objective-CRealm Objective-C和Realm Swift API不可互操做,不支持它们一块儿使用。git

Realm Swift使您可以以安全,持久和快速的方式有效地编写应用程序的模型层。这是它的样子:github

 1 // Define your models like regular Swift classes
 2 class Dog: Object {
 3     @objc dynamic var name = ""
 4     @objc dynamic var age = 0
 5 }
 6 class Person: Object {
 7     @objc dynamic var name = ""
 8     @objc dynamic var picture: Data? = nil // optionals supported
 9     let dogs = List<Dog>()
10 }
11 
12 // Use them like regular Swift objects
13 let myDog = Dog()
14 myDog.name = "Rex"
15 myDog.age = 1
16 print("name of dog: \(myDog.name)")
17 
18 // Get the default Realm
19 let realm = try! Realm()
20 
21 // Query Realm for all dogs less than 2 years old
22 let puppies = realm.objects(Dog.self).filter("age < 2")
23 puppies.count // => 0 because no dogs have been added to the Realm yet
24 
25 // Persist your data easily
26 try! realm.write {
27     realm.add(myDog)
28 }
29 
30 // Queries are updated in realtime
31 puppies.count // => 1
32 
33 // Query and update from any thread
34 DispatchQueue(label: "background").async {
35     autoreleasepool {
36         let realm = try! Realm()
37         let theDog = realm.objects(Dog.self).filter("age == 1").first
38         try! realm.write {
39             theDog!.age = 3
40         }
41     }
42 }

Realm Studio

Realm Studio是咱们的首选开发人员工具,能够轻松管理Realm数据库和Realm平台。使用Realm Studio,您能够打开和编辑本地和同步的域,并管理任何Realm Object Server实例。它支持Mac,Windows和Linux。数据库

Realm Studio

使用菜单项“ 工具”>“生成演示数据库”建立包含示例数据的测试数据库后端

若是您在查找应用程序的Realm文件时须要帮助,请查看此StackOverflow答案以获取详细说明。

例子

您能够在咱们的发布zip找到iOS和OS X的示例应用程序examples/,演示如何使用Realm的许多功能,如迁移,如何使用它UITableViewController,加密,命令行工具等等。

使用Realm框架

在Swift源文件的顶部,用于import RealmSwift导入Realm Swift并使其可用于您的代码。这就是你开始所须要的一切!

tvOS

由于在tvOS上禁止写入“Documents”目录,因此默认的Realm位置设置为NSCachesDirectory可是,请注意tvOS能够随时清除“Caches”目录中的文件,所以咱们建议您依赖Realm做为可重建的缓存,而不是存储重要的用户数据。

若是您想在tvOS应用程序和电视服务扩展(例如Top Shelf扩展)之间共享Realm文件,则必须使用Library/Caches/共享容器中的应用程序组目录。

1 let fileURL = FileManager.default
2     .containerURL(forSecurityApplicationGroupIdentifier: "group.io.realm.examples.extension")!
3     .appendingPathComponent("Library/Caches/default.realm")

您还能够在应用中捆绑预构建的Realm文件可是,请务必遵照App Store指南,将您的应用保持在200MB如下。请浏览咱们的tvOS示例,了解示例如何使用Realm做为离线缓存或预加载数据的示例tvOS应用程序。

使用Realm与后台应用程序刷新

在iOS 8及更高版本中,NSFileProtection只要设备被锁定,应用程序内的文件就会自动加密若是您的应用程序在设备被锁定时尝试执行涉及Realm的任何工做,而且NSFileProtection您的Realm文件属性设置为加密它们(默认状况下就是这种状况),open() failed: Operation not permitted则会引起异常。

为了解决这个问题,有必要确保应用于Realm文件自己及其辅助文件的文件保护属性降级为不太严格的文件保护属性,即便在设备被锁定时也容许文件访问,例如NSFileProtectionCompleteUntilFirstUserAuthentication

若是您选择以这种方式选择退出完整的iOS文件加密,咱们建议您使用Realm本身的内置加密来确保您的数据仍然获得妥善保护。

因为辅助文件有时能够在操做过程当中延迟建立和删除,所以咱们建议您将文件保护属性应用于包含这些Realm文件的父文件夹。这将确保该属性正确应用于全部相关Realm文件,不管其建立时间如何。

1 let realm = try! Realm()
2 
3 // Get our Realm file's parent directory
4 let folderPath = realm.configuration.fileURL!.deletingLastPathComponent().path
5 
6 // Disable file protection for this directory
7 try! FileManager.default.setAttributes([FileAttributeKey(rawValue: NSFileProtectionKey): NSFileProtectionNone],
8                                        ofItemAtPath: folderPath)

三界

一个境界是一种境界移动数据库容器的一个实例。

有关Realms的详细讨论,请阅读The Realm Data Model有关建立和管理领域的信息,请参阅

打开本地领域

要打开Realm,请实例化一个新Realm对象:

1 let realm = try! Realm()
2 
3 try! realm.write {
4     realm.add(myDog)
5 }

这会实例化默认的Realm

配置本地领域

经过建立实例Realm.Configuration并设置适当的属性,在打开Realm以前配置它建立和自定义配置值容许您自定义以及其余方面:

  • 本地Realm文件位置的路径
  • 迁移功能,若是一个领域的模式和版本之间的更改必须更新
  • 配置压缩功能以确保有效利用磁盘空间。

能够在Realm(configuration: config)每次须要Realm实例时传递配置,也能够将配置设置为默认Realm实例Realm.Configuration.defaultConfiguration = config

例如,假设您有一个应用程序,用户必须登陆到您的Web后端,而且您但愿支持在账户之间快速切换。您能够经过执行如下操做为每一个账户提供本身的Realm文件,该文件将用做默认Realm:

1 func setDefaultRealmForUser(username: String) {
2     var config = Realm.Configuration()
3 
4     // Use the default directory, but replace the filename with the username
5     config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("\(username).realm")
6 
7     // Set this as the configuration used for the default Realm
8     Realm.Configuration.defaultConfiguration = config
9 }

您能够拥有多个配置对象,所以您能够独立控制每一个Realm的版本,架构和位置。

 1 let config = Realm.Configuration(
 2     // Get the URL to the bundled file
 3     fileURL: Bundle.main.url(forResource: "MyBundledData", withExtension: "realm"),
 4     // Open the file in read-only mode as application bundles are not writeable
 5     readOnly: true)
 6 
 7 // Open the Realm with the configuration
 8 let realm = try! Realm(configuration: config)
 9 
10 // Read some data from the bundled Realm
11 let results = realm.objects(Dog.self).filter("age > 5")

存储可写Realm文件的最多见位置是iOS上的“Documents”目录和macOS上的“Application Support”目录。请尊重Apple的iOS数据存储指南,该指南建议若是应用程序能够从新生成的文档应存储在<Application_Home>/Library/Caches目录中。若是使用自定义URL初始化Realm,则必须描述具备写入权限的位置。

默认领域

到目前为止,您可能已经注意到咱们realm经过调用初始化了对变量的访问Realm()该方法返回一个Realm对象,对象映射到default.realm应用程序的Documents文件夹(iOS)或Application Support文件夹(macOS)中指定的文件。

打开同步领域

您是否但愿使用Realm Mobile Platform同步全部Realm数据库?全部与同步相关的文档已移至咱们的平台文档中

内存领域

经过设置inMemoryIdentifier而不是fileURLon Realm.Configuration,您能够建立一个彻底在内存中运行而不会持久保存到磁盘的Realm。设置inMemoryIdentifier将为零fileURL(反之亦然)。

let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "MyInMemoryRealm"))

内存领域不会跨应用程序启动保存数据,但Realm的全部其余功能将按预期工做,包括查询,关系和线程安全。若是您须要灵活的数据访问而没有磁盘持久性的开销,这是一个有用的选项。

内存领域在临时目录中建立多个文件,用于协调跨进程通知等事务。实际上没有数据写入文件,除非因为内存压力操做系统须要交换到磁盘。

注意:当具备特定标识符的全部内存中Realm实例超出范围而没有引用时,该Realm中的全部数据都将被删除。咱们建议您在应用程序的生命周期内保留对任何内存领域的强引用。(对于磁盘领域,这不是必需的。)

错误处理

与任何磁盘I / O操做同样,Realm若是资源受到限制,建立实例有时可能会失败。实际上,这只能在第一次在给定线程上建立Realm实例时发生。从同一个线程对Realm的后续访问将重用高速缓存的实例并始终成功。

要在首次访问给定线程上的Realm时处理错误,请使用Swift的内置错误处理机制:

1 do {
2     let realm = try Realm()
3 } catch let error as NSError {
4     // handle error
5 }

辅助领域文件

除标准.realm文件外,Realm还为其本身的内部操做生成并维护其余文件和目录。

  • .realm.lock - 资源锁的锁文件。
  • .realm.management - 进程间锁定文件的目录。
  • .realm.note - 用于通知的命名管道。

这些文件对.realm数据库文件没有任何影响,若是删除或替换父数据库文件,则不会致使任何错误行为。

报告领域的问题,请必定要包括这些辅助文件与主一块儿.realm的文件,由于它们包含用于调试的信息。

捆绑一个境界

一般使用初始数据为应用程序设定种子,使其在首次启动时当即可供您的用户使用。这是如何作到这一点:

  1. 首先,填充领域。您应该使用与最终发货应用相同的数据模型来建立Realm,并使用您但愿与应用捆绑在一块儿的数据填充它。因为Realm文件是跨平台的,您可使用macOS应用程序(请参阅咱们的JSONImport示例)或在模拟器中运行的iOS应用程序。
  2. 在您生成此Realm文件的代码中,您应该经过制做文件的压缩副原本完成(请参阅参考资料Realm().writeCopyToPath(_:encryptionKey:))。这将减小Realm的文件大小,使您的最终应用程序更轻松地为您的用户下载。
  3. 将Realm文件的新压缩副本拖到最终应用程序的Xcode Project Navigator中。
  4. 转到Xcode中的app target的构建阶段选项卡,并将Realm文件添加到“Copy Bundle Resources”构建阶段。
  5. 此时,您的应用能够访问捆绑的Realm文件。您可使用找到它的路径NSBundle.main.pathForResource(_:ofType:)
  6. 若是捆绑的领域包含您不须要修改固定的数据,你能够直接从束路径设置中打开它readOnly = true的上Realm.Configuration对象。不然,若是它是您要修改的初始数据,则可使用将捆绑的文件复制到应用程序的Documents目录中NSFileManager.default.copyItemAtPath(_:toPath:)

您能够参考咱们的迁移示例应用程序,以获取有关如何使用捆绑的Realm文件的示例。

类子集

在某些状况下,您可能但愿限制哪些类能够存储在特定领域中。例如,若是您有两个团队在应用程序的不一样组件上工做,这两个组件都在内部使用Realm,那么您可能不但愿必须协调它们之间的迁移你能够经过设置objectTypes属性来作到这一点Realm.Configuration

1 let config = Realm.Configuration(objectTypes: [MyClass.self, MyOtherClass.self])
2 let realm = try! Realm(configuration: config)

压缩领域

Realm的工做方式是Realm文件的大小始终大于存储在其中的对象的总大小。请参阅咱们关于线程的文档,了解为何这种架构可以实现Realm的一些出色性能,并发性和安全性优点。

为了不进行昂贵的系统调用,Realm文件不多在运行时缩小。相反,它们以特定的大小增量增加,新数据被写入文件内跟踪的未使用空间内。可是,可能存在Realm文件的重要部分由未使用的空间组成的状况。为了解决这个问题,您能够shouldCompactOnLaunch在Realm的配置对象上设置block属性,以肯定在第一次打开时是否应该压缩Realm文件。例如:

 1 let config = Realm.Configuration(shouldCompactOnLaunch: { totalBytes, usedBytes in
 2     // totalBytes refers to the size of the file on disk in bytes (data + free space)
 3     // usedBytes refers to the number of bytes used by data in the file
 4 
 5     // Compact if the file is over 100MB in size and less than 50% 'used'
 6     let oneHundredMB = 100 * 1024 * 1024
 7     return (totalBytes > oneHundredMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5
 8 })
 9 do {
10     // Realm is compacted on the first open if the configuration block conditions were met.
11     let realm = try Realm(configuration: config)
12 } catch {
13     // handle error compacting or opening Realm
14 }

压缩操做经过读取Realm文件的所有内容,将其重写到不一样位置的新文件,而后替换原始文件来工做。根据文件中的数据量,这多是一项昂贵的操做。

咱们鼓励您尝试使用这些数字来肯定在常常执行压缩和让Realm文件变得过大之间取得良好平衡。

最后,若是另外一个进程正在访问Realm,即便知足配置块的条件,也会跳过压缩。这是由于在访问Realm时没法安全地执行压缩。

shouldCompactOnLaunch同步域不支持设置块。这是由于压缩不会保留事务日志,必须保留事务日志以进行同步。

删除Realm文件

在某些状况下,例如清除缓存或重置整个数据集,从磁盘中彻底删除Realm文件多是合适的。

由于Realm避免将数据复制到内存中,除非绝对须要,因此Realm管理的全部对象都包含对磁盘上文件的引用,而且必须先释放它才能安全删除文件。这包括从读取(或加入)的全部对象的境界,全部ListResults以及ThreadSafeReference目的和Realm自己。

实际上,这意味着删除Realm文件应该在应用程序启动以前在打开Realm以前完成,或者在仅在显式自动释放池中打开Realm以后完成,这样能够确保全部Realm对象都已被释放。

最后,虽然不是绝对必要,但您应该删除辅助Realm文件以及主Realm文件以彻底清除全部相关文件。

 1 autoreleasepool {
 2     // all Realm usage here
 3 }
 4 let realmURL = Realm.Configuration.defaultConfiguration.fileURL!
 5 let realmURLs = [
 6     realmURL,
 7     realmURL.appendingPathExtension("lock"),
 8     realmURL.appendingPathExtension("note"),
 9     realmURL.appendingPathExtension("management")
10 ]
11 for URL in realmURLs {
12     do {
13         try FileManager.default.removeItem(at: URL)
14     } catch {
15         // handle error
16     }
17 }

楷模

领域数据模型被定义为具备常规属性的常规Swift类。建立一个,只是子类Object或现有的Realm模型类。领域模型对象的功能大多与其余任何Swift对象同样。您能够在它们上定义本身的方法,使它们符合协议,并像使用任何其余对象同样使用它们。主要限制是您只能在建立它的线程上使用对象。

关系和嵌套的数据结构由包括目标类型的属性或建模List小号的对象的类型的列表。List实例也可用于建模原始值的集合(例如,字符串或整数数组)。

 1 import RealmSwift
 2 
 3 // Dog model
 4 class Dog: Object {
 5     @objc dynamic var name = ""
 6     @objc dynamic var owner: Person? // Properties can be optional
 7 }
 8 
 9 // Person model
10 class Person: Object {
11     @objc dynamic var name = ""
12     @objc dynamic var birthdate = Date(timeIntervalSince1970: 1)
13     let dogs = List<Dog>()
14 }

因为Realm在启动时会解析代码中定义的全部模型,所以它们必须所有有效,即便它们从未使用过。

当使用Swift中的Realm时,该Swift.reflect(_:)函数用于肯定有关模型的信息,这须要调用init()成功。这意味着全部非可选属性都必须具备默认值。

有关详细信息,请参阅咱们的API文档Object

支持的属性类型

域支持如下属性类型:BoolIntInt8Int16Int32Int64DoubleFloatStringDate,和Data

CGFloat 不鼓励使用属性,由于类型不是平台无关的。

StringDate而且Data属性能够是可选的。Object属性必须是可选的。存储可选数字是使用RealmOptional

必需的属性

StringDateData属性可使用标准Swift语法声明为可选或必需(非可选)。使用类型声明可选的数字类型RealmOptional

 1 class Person: Object {
 2     // Optional string property, defaulting to nil
 3     @objc dynamic var name: String? = nil
 4 
 5     // Optional int property, defaulting to nil
 6     // RealmOptional properties should always be declared with `let`,
 7     // as assigning to them directly will not work as desired
 8     let age = RealmOptional<Int>()
 9 }
10 
11 let realm = try! Realm()
12 try! realm.write() {
13     var person = realm.create(Person.self, value: ["Jane", 27])
14     // Reading from or modifying a `RealmOptional` is done via the `value` property
15     person.age.value = 28
16 }

RealmOptional支持IntFloatDoubleBool,和全部的大小版本IntInt8Int16Int32Int64)。

主键

覆盖Object.primaryKey()以设置模型的主键。声明主键能够有效地查找和更新对象,并为每一个值强制实现惟一性。将具备主键的对象添加到Realm后,没法更改主键。

1 class Person: Object {
2     @objc dynamic var id = 0
3     @objc dynamic var name = ""
4 
5     override static func primaryKey() -> String? {
6         return "id"
7     }
8 }

索引属性

要索引属性,请覆盖Object.indexedProperties()与主键同样,索引使写入速度稍慢,但使查询使用相等性和IN运算符更快。(它还会使您的Realm文件略大,以存储索引。)最好只在优化特定状况下的读取性能时添加索引。

1 class Book: Object {
2     @objc dynamic var price = 0
3     @objc dynamic var title = ""
4 
5     override static func indexedProperties() -> [String] {
6         return ["title"]
7     }
8 }

Realm支持对字符串,整数,布尔值和Date属性进行索引

忽略属性

若是您不想将模型中的字段保存到其Realm,请覆盖Object.ignoredProperties()领域不会干扰这些属性的正常运行; 他们将获得伊娃的支持,你能够自由地覆盖他们的二传手和吸气者。

 1 class Person: Object {
 2     @objc dynamic var tmpID = 0
 3     var name: String { // read-only properties are automatically ignored
 4         return "\(firstName) \(lastName)"
 5     }
 6     @objc dynamic var firstName = ""
 7     @objc dynamic var lastName = ""
 8 
 9     override static func ignoredProperties() -> [String] {
10         return ["tmpID"]
11     }
12 }

忽略的属性与普通属性彻底相同。它们不支持任何特定于Realm的功能(例如,它们不能在查询中使用,也不会触发通知)。仍然可使用KVO观察它们。

属性属性

领域模型属性必须具备该@objc dynamic var属性才能成为底层数据库数据的访问者。请注意,若是将类声明为@objcMembers(Swift 4或更高版本),则能够将各个属性声明为dynamic var

有三种例外状况:LinkingObjectsListRealmOptional这些属性不能声明为动态,由于通用属性没法在Objective-C运行时中表示,后者用于动态分派dynamic属性。应始终使用声明这些属性let

财产备忘单

此表提供了声明模型属性的便捷参考。

类型 非可选 可选的
布尔 @objc dynamic var value = false let value = RealmOptional<Bool>()
诠释 @objc dynamic var value = 0 let value = RealmOptional<Int>()
浮动 @objc dynamic var value: Float = 0.0 let value = RealmOptional<Float>()
@objc dynamic var value: Double = 0.0 let value = RealmOptional<Double>()
@objc dynamic var value = "" @objc dynamic var value: String? = nil
数据 @objc dynamic var value = Data() @objc dynamic var value: Data? = nil
日期 @objc dynamic var value = Date() @objc dynamic var value: Date? = nil
宾语 不适用:必须是可选的 @objc dynamic var value: Class?
名单 let value = List<Type>() 不适用:必须是非选择性的
LinkingObjects let value = LinkingObjects(fromType: Class.self, property: "property") 不适用:必须是非选择性的

使用Realm对象

自动更新对象

Object实例是实时的,自动更新基础数据的视图; 你永远没必要刷新对象。修改对象的属性将当即反映在引用同一对象的任何其余实例中。

 1 let myDog = Dog()
 2 myDog.name = "Fido"
 3 myDog.age = 1
 4 
 5 try! realm.write {
 6     realm.add(myDog)
 7 }
 8 
 9 let myPuppy = realm.objects(Dog.self).filter("age == 1").first
10 try! realm.write {
11     myPuppy!.age = 2
12 }
13 
14 print("age of my dog: \(myDog.age)") // => 2

这不只能够保持Realm的快速和高效,还可使您的代码更简单,更具反应性。若是您的UI代码依赖于特定的Realm对象,则在触发UI重绘以前,您无需担忧刷新或从新获取它。

您能够订阅Realm通知,以了解对象中的Realm数据什么时候更新,指示什么时候应刷新应用程序的UI。

模型继承

Realm容许模型进一步子类化,容许跨模型重用代码,可是一些致使运行时富类多态的Cocoa特性不可用。这是可能的:

  • 父类的类方法,实例方法和属性在其子类中继承。
  • 将父类做为参数的方法和函数能够在子类上运行。

目前没法实现如下目标:

  • 多态类之间的转换(即,子类到子类,子类到父类,父类到子类等)
  • 同时查询多个类
  • 多级容器(ListResults

将此功能添加到Realm是路线图目前,咱们提供了一些代码示例,用于解决一些更常见的模式。

或者,若是您的实现容许,咱们建议使用如下类组合模式来构建包含来自其余类的逻辑的子类:

 1 // Base Model
 2 class Animal: Object {
 3     @objc dynamic var age = 0
 4 }
 5 
 6 // Models composed with Animal
 7 class Duck: Object {
 8     @objc dynamic var animal: Animal? = nil
 9     @objc dynamic var name = ""
10 }
11 class Frog: Object {
12     @objc dynamic var animal: Animal? = nil
13     @objc dynamic var dateProp = Date()
14 }
15 
16 // Usage
17 let duck = Duck(value: [ "animal": [ "age": 3 ], "name": "Gustav" ])

集合

Realm有几种类型能够帮助表示对象组,咱们称之为“Realm集合”:

  1. Results,一个表示从查询中检索的对象的类
  2. List,一个表示模型中多对多关系的类
  3. LinkingObjects,一个表示模型中反比关系的类
  4. RealmCollection,一个定义全部Realm集合符合的公共接口的协议。
  5. AnyRealmCollection,一个类型擦除的类,能够转发到具体的Realm集合,如ResultsListLinkingObjects

Realm集合类型各自符合RealmCollection协议,这确保它们的行为一致。该协议的继承CollectionType使得它能够以与其余标准库集合相同的方式使用。在此协议中声明了其余常见的Realm集合API,例如查询,排序和聚合操做等。Lists具备超出协议接口的额外变异操做,例如添加和删除对象或值。

使用该RealmCollection协议,您能够编写能够在任何Realm集合上运行的通用代码:

1 func operateOn<C: RealmCollection>(collection: C) {
2     // Collection could be either Results or List
3     print("operating on collection containing \(collection.count) objects")
4 }

因为与斯威夫特的类型系统的局限性,有必要使用类型擦除的包装,例如AnyRealmCollection,以存储该集合做为一个属性或变量:

 1 class ViewController {
 2 //    let collection: RealmCollection
 3 //                    ^
 4 //                    error: protocol 'RealmCollection' can only be used
 5 //                    as a generic constraint because it has Self or
 6 //                    associated type requirements
 7 //
 8 //    init<C: RealmCollection>(collection: C) where C.ElementType == MyModel {
 9 //        self.collection = collection
10 //    }
11 
12     let collection: AnyRealmCollection<MyModel>
13 
14     init<C: RealmCollection>(collection: C) where C.ElementType == MyModel {
15         self.collection = AnyRealmCollection(collection)
16     }
17 }

在领域之间复制对象

将Realm对象复制到其余Realms就像传入原始对象同样简单Realm().create(_:value:update:)例如,realm.create(MyObjectSubclass.self, value: originalObjectInstance)请记住,Realm对象只能从首次建立它们的线程中访问,所以该副本仅适用于同一线程上的Realms。

请注意,Realm().create(_:value:update:)不支持处理循环对象图。不要直接或间接传入包含涉及引用其父项的对象的关系的对象。

关系

您能够将任意两个Realm对象连接在一块儿。Realm中的关系很便宜:遍历连接在速度或内存方面并不昂贵。让咱们探索不一样类型的关系,Realm容许您在对象之间进行定义。

Object经过使用ObjectList属性连接a Lists具备很是相似的接口Array,而且List可使用索引下标来访问a中包含的对象Array不一样Lists只保存Object单个子类的类型。有关更多详细信息,请参阅API文档List

假设您的Person模型已经定义(参见模型),让咱们建立一个名为的模型Dog

1 class Dog: Object {
2     @objc dynamic var name = ""
3 }

许多到一

要设置多对一或一对一关系,请为模型提供其类型为您的Object子类之一的属性

1 class Dog: Object {
2     // ... other property declarations
3     @objc dynamic var owner: Person? // to-one relationships must be optional
4 }

您能够像使用任何其余属性同样使用此属性:

1 let jim = Person()
2 let rex = Dog()
3 rex.owner = jim

使用Object属性时,可使用常规属性语法访问嵌套属性。例如,rex.owner?.address.country将遍历对象图并根据须要自动从Realm中获取每一个对象。

许多一对多

您可使用List属性建立与任意数量的对象或支持的原始值的关系Lists包含Object单个类型的其余s或原始值,而且具备与mutable很是类似的接口Array

List包含Realm对象的s能够存储对同一Realm对象的多个引用,包括具备主键的对象。例如,您能够建立一个空的List并将相同的对象插入其中三次; List而后将返回若是元素该对象在任何索引0,1和2被访问。

Lists能够存储原始值来代替Realm对象。为此,简单地定义一个List含有BoolIntInt8Int16Int32Int64FloatDoubleStringData,或Date值,或任何上述类型的可选版本。

dogs在咱们的Person模型添加连接到多个狗的属性,咱们能够声明类型的属性List<Dog>

1 class Person: Object {
2     // ... other property declarations
3     let dogs = List<Dog>()
4 }

您能够List照常访问和分配属性:

1 let someDogs = realm.objects(Dog.self).filter("name contains 'Fido'")
2 jim.dogs.append(objectsIn: someDogs)
3 jim.dogs.append(rex)

List 保证属性保持其插入顺序。

请注意,List当前不支持查询包含原始值的s。

反向关系

关系是单向的。就拿咱们的两个类Person,并Dog做为一个例子。若是Person.dogs连接到Dog实例,则能够按照连接从Persona到a Dog,可是没法从a Dog到其Person对象。您能够设置Dog.owner连接到的一对一属性Person,但这些连接彼此独立。添加一个Dogto Person.dogs不会将该狗的Dog.owner属性设置为正确Person为解决此问题,Realm提供连接对象属性以表示反向关系。

1 class Dog: Object {
2     @objc dynamic var name = ""
3     @objc dynamic var age = 0
4     let owners = LinkingObjects(fromType: Person.self, property: "dogs")
5 }

经过连接对象属性,您能够从特定属性获取连接到给定对象的全部对象。一个Dog对象能够有一个名为属性owners包含全部的Person有这个确切的对象Dog在他们的对象dogs属性。建立owners类型属性,LinkingObjects而后指定它与Person对象的关系

对象的全部更改(添加,修改和删除)必须在写入事务中完成。

Realm对象能够实例化并用做非托管对象(即还没有添加到Realm),就像常规的Swift对象同样。可是,要在线程之间共享对象或在应用程序启动之间从新使用它们,必须将它们添加到Realm。向Realm添加对象必须在写入事务中完成。因为写入事务会产生不可忽略的开销,所以您应该构建代码以最大限度地减小写入事务的数量。

领域写操做是同步和阻塞,而不是异步。若是线程A开始写操做,则线程B在线程A完成以前在同一个域上开始写操做,线程A必须在线程B的写操做发生以前完成并提交其事务。写操做始终自动刷新beginWrite(),所以重叠写入不会建立竞争条件。

由于写事务可能会失败,就像任何其余的磁盘IO操做,都Realm.write()Realm.commitWrite()被标记为throws这样你就能够处理,并从失败就像跑出来的磁盘空间进行恢复。没有其余可恢复的错误。为简洁起见,咱们的代码示例不处理这些错误,但您确定应该在生产应用程序中。

建立对象

定义模型后,能够实例化子Object类并将新实例添加到Realm。考虑这个简单的模型:

1 class Dog: Object {
2     @objc dynamic var name = ""
3     @objc dynamic var age = 0
4 }

咱们能够用几种方式建立新对象:

 1 // (1) Create a Dog object and then set its properties
 2 var myDog = Dog()
 3 myDog.name = "Rex"
 4 myDog.age = 10
 5 
 6 // (2) Create a Dog object from a dictionary
 7 let myOtherDog = Dog(value: ["name" : "Pluto", "age": 3])
 8 
 9 // (3) Create a Dog object from an array
10 let myThirdDog = Dog(value: ["Fido", 5])
  1. 最明显的是使用指定的初始化程序来建立对象。
  2. 也可使用适当的键和值从字典建立对象。
  3. 最后,Object可使用数组实例化子类。数组中的值必须与模型中的相应属性的顺序相同。

数组中的值应与存储在Realm中的属性相对应 - 您不该指定忽略的属性或计算属性的值。

建立对象后,能够将其添加到Realm:

1 // Get the default Realm
2 let realm = try! Realm()
3 // You only need to do this once (per thread)
4 
5 // Add to the Realm inside a transaction
6 try! realm.write {
7     realm.add(myDog)
8 }

将对象添加到Realm后,您能够继续使用它,而且您对其所作的全部更改都将被保留(而且必须在写入事务中进行)。在提交写入事务时,对使用相同Realm的其余线程能够进行任何更改。

请注意,写入会相互阻塞,而且若是正在进行屡次写入,则会阻止它们建立的线程。这相似于其余持久性解决方案,咱们建议您在这种状况下使用一般的最佳实践:将写入卸载到单独的线程。

因为Realm的MVCC架构,在写事务打开时不会阻止读取。除非您须要同时从多个线程同时进行写入,不然您应该支持更大的写入事务,这些事务对许多细粒度的写入事务执行更多操做。当您向Realm提交写入事务时,将通知该Realm的全部其余实例,并自动更新

有关详细信息,请参阅领域对象

嵌套对象

若是对象具备Objects或List属性,则可使用嵌套数组和/或字典递归设置这些属性您只需使用表示其属性的字典或数组替换每一个对象:

1 // Instead of using already existing dogs...
2 let aPerson = Person(value: ["Jane", 30, [aDog, anotherDog]])
3 
4 // ...we can create them inline
5 let anotherPerson = Person(value: ["Jane", 30, [["Buster", 5], ["Buddy", 6]]])

这适用于嵌套数组和字典的任意组合。请注意,a List可能只包含Objects,而不是基本类型String

更新对象

Realm提供了一些更新对象的方法,全部这些方法都根据具体状况提供不一样的权衡。

键入的更新

您能够经过在写入事务中设置其属性来更新任何对象。

1 // Update an object with a transaction
2 try! realm.write {
3     author.name = "Thomas Pynchon"
4 }

键值编码

ObjectResultList全部符合键值编码(KVC)。当您须要肯定在运行时更新哪一个属性时,这很是有用。

将KVC应用于集合是批量更新对象的好方法,而不会在为每一个项建立访问器时迭代集合。

1 let persons = realm.objects(Person.self)
2 try! realm.write {
3     persons.first?.setValue(true, forKeyPath: "isFirst")
4     // set each person's planet property to "Earth"
5     persons.setValue("Earth", forKeyPath: "planet")
6 }

具备主键的对象

若是模型类包含主键,则可使用Realm智能更新或基于主键值添加对象Realm().add(_:update:)

 1 // Creating a book with the same primary key as a previously saved book
 2 let cheeseBook = Book()
 3 cheeseBook.title = "Cheese recipes"
 4 cheeseBook.price = 9000
 5 cheeseBook.id = 1
 6 
 7 // Updating book with id = 1
 8 try! realm.write {
 9     realm.add(cheeseBook, update: .modified)
10 }

若是Book数据库中已存在主键值为“1”的对象,则只会更新该对象。若是它不存在,则将Book建立一个全新的对象并将其添加到数据库中。

您还能够经过仅传递要更新的值的子集以及主键来部分更新具备主键的对象:

1 // Assuming a "Book" with a primary key of `1` already exists.
2 try! realm.write {
3     realm.create(Book.self, value: ["id": 1, "price": 9000.0], update: .modified)
4     // the book's `title` property will remain unchanged.
5 }

您可能没法传递update: .modified或未传递update: .all未定义主键的对象类型。

当更新的对象,你能够选择要么具备全部的属性设置为现有对象传入的值,或只对已经经过传递任何实际更改成新值的属性.modified.allupdate: 这个决定有一些影响:

  1. 产生了什么通知。使用对象通知时.all将报告value传递的对象中存在的全部属性都已修改,同时.modified将仅致使报告具备新值的属性。
  2. 使用Realm Object Server时如何合并冲突的写入。假设您有一本书的标题为奶酪食谱,价格为9000,而且一个客户与另外一个客户realm.create(Book.self, value: ["id": 1, title: "Fruit recipes", price: 9000], update: .all)同时打电话realm.create(Book.self, value: ["id": 1, title: "Cheese recipes", price: 4000], update: .all)由于全部属性都已设置,因此合并后的结果将是一本书的标题为奶酪食谱,价格为4000或一本书的标题为水果食谱,价格为9000.若是相反,他们经过.modified的结果将是一本书标题为水果食谱,价格为4000。
  3. 性能。检查属性是否已更改有少许开销.modified可是,若是属性未更改,.all则会写入更多数据,这二者都会增长必须写入本地Realm的数据量,并增长Realm对象服务器须要处理的指令数。

若是有疑问,.modified可能就是你想要的那个。

请注意,更新对象时,nil仍被视为可选属性的有效值若是您提供具备nil属性值的字典,则这些字典将应用于您的对象,而且这些属性将被清空。为确保您不会遇到任何计划外数据丢失,请确保在使用此方法时仅提供您要更新的属性。

删除对象

将要删除的对象传递Realm().delete(_:)给写入事务中方法。

1 // let cheeseBook = ... Book stored in Realm
2 
3 // Delete an object with a transaction
4 try! realm.write {
5     realm.delete(cheeseBook)
6 }

您还能够删除存储在Realm中的全部对象。请注意,Realm文件将在磁盘上保持其大小,以便有效地将该空间重用于未来的对象。

1 // Delete all objects from the realm
2 try! realm.write {
3     realm.deleteAll()
4 }

查询

查询返回一个Results实例,其中包含Object的集合Results有一个很是类似的接口,Array而且Results可使用索引下标访问a中包含的对象Array不一样Results只保留Object单个子类的s。

全部查询(包括查询和属性访问)在Realm中都是惰性的。只有在访问属性时才会读取数据。

查询的结果不是数据的副本:修改查询结果(在写入事务中)将直接修改磁盘上的数据。一样,您能够直接从a中包含遍历关系ObjectResults

延迟执行查询直到使用结果。这意味着将几个临时连接Results以对数据进行排序和过滤不会执行处理中间状态的额外工做。

一旦执行了查询,或者添加通知块Results就会更新Realm中的更改,并在可能的状况下在后台线程上执行查询。

从Realm中检索对象的最基本方法是Realm().objects(_:),返回从默认Realm查询的子类类型的全部Object实例的Result。

let dogs = realm.objects(Dog.self) // retrieves all Dogs from the default Realm

过滤

若是您熟悉NSPredicate,那么您已经知道如何在Realm中查询。ObjectsRealmList,和Results全部提供容许您查询具体方法为Object经过简单地传递一个实例NSPredicate的实例,谓语字符串,或者就像你的查询谓词时格式字符串NSArray

例如,如下内容将经过调用Results().filter(_:...)从默认Realm中检索名称以“B”开头的全部棕褐色狗来扩展咱们以前的示例

1 // Query using a predicate string
2 var tanDogs = realm.objects(Dog.self).filter("color = 'tan' AND name BEGINSWITH 'B'")
3 
4 // Query using an NSPredicate
5 let predicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "tan", "B")
6 tanDogs = realm.objects(Dog.self).filter(predicate)

有关构建谓词和使用咱们的NSPredicate Cheatsheet的更多信息,请参阅Apple的Predicates编程指南Realm支持许多常见谓词:

  • 比较操做数能够是属性名称或常量。至少有一个操做数必须是属性名称。
  • 比较操做符==<= <> =>!=,和BETWEEN都支持IntInt8Int16Int32Int64FloatDoubleDate属性类型,例如age == 45
  • 身份比较==!=,例如Results<Employee>().filter("company == %@", company)
  • 布尔属性支持比较运算符==!=
  • 对于StringData属性,支持==!=BEGINSWITHCONTAINSENDSWITH运算符,例如name CONTAINS 'Ja'
  • 对于String属性,LIKE运算符可用于将左手属性与右手表达式进行比较:?而且*容许做为通配符,其中?匹配1个字符并*匹配0个或更多个字符。示例:value LIKE '?bc*'匹配“abcde”和“cbc”等字符串。
  • 字符串的不区分大小写的比较,例如name CONTAINS[c] 'Ja'请注意,只有字符“AZ”和“az”才会被忽略。[c] modifier can be combined with the [d]`改性剂。
  • 字符串的变音符号不敏感比较,例如name BEGINSWITH[d] 'e'匹配étoile此修饰符可与[c]修饰符组合使用(此修饰符只能应用于Realm支持的字符串子集:请参阅详细信息的限制。)
  • Realm支持如下复合运算符:“AND”“OR”“NOT”,例如name BEGINSWITH 'J' AND age >= 32
  • 收容操做数IN,例如name IN {'Lisa', 'Spike', 'Hachi'}
  • 无比较==!=,例如Results<Company>().filter("ceo == nil")请注意,Realm将其nil视为特殊值而不是缺乏值; 与SQL不一样,nil等于本身。
  • 任何比较,例如ANY student.age < 21
  • 支持属性的聚合表达式@ count@ min@ max@ sum@avg,例如,查找全部员工人数超过五人的公司。ListResultsrealm.objects(Company.self).filter("employees.@count > 5")
  • 子查询受如下限制支持:
    • @count是惟一能够应用于SUBQUERY表达式的运算符
    • SUBQUERY(…).@count表达式必须以恒定的相比较。
    • 尚不支持相关的子查询。

Results().filter(_:...)

排序

Results容许您根据键路径,属性或一个或多个排序描述符指定排序条件和顺序。例如,如下调用按名称按字母顺序对上面示例中返回的狗进行排序:

// Sort tan dogs with names starting with "B" by name let sortedDogs = realm.objects(Dog.self).filter("color = 'tan' AND name BEGINSWITH 'B'").sorted(byKeyPath: "name")

关键路径也多是一对一关系的属性

 1 class Person: Object {
 2     @objc dynamic var name = ""
 3     @objc dynamic var dog: Dog?
 4 }
 5 class Dog: Object {
 6     @objc dynamic var name = ""
 7     @objc dynamic var age = 0
 8 }
 9 
10 let dogOwners = realm.objects(Person.self)
11 let ownersByDogAge = dogOwners.sorted(byKeyPath: "dog.age")

请注意,sorted(byKeyPath:)而且sorted(byProperty:)不支持多个属性做为排序条件,而且不能连接(仅使用最后一次调用sorted)。要按多个属性排序,请使用sorted(by:)具备多个SortDescriptor对象方法

有关更多信息,请参阅

请注意,Results仅在查询排序时保证顺序保持一致。出于性能缘由,不保证保留插入顺序。若是您须要维护插入顺序,这里提出一些解决方案

连接查询

与须要为每一个连续查询单独访问数据库服务器的传统数据库相比,Realm查询引擎的一个独特属性是可以以很是小的事务开销连接查询。

若是你想要一个棕褐色狗的结果集,以及名字也以'B'开头的棕褐色狗,你能够连接两个这样的查询:

1 let tanDogs = realm.objects(Dog.self).filter("color = 'tan'")
2 let tanDogsWithBNames = tanDogs.filter("name BEGINSWITH 'B'")

自动更新结果

Results实例是实时的,自动更新基础数据的视图,这意味着永远没必要从新获取结果。它们老是在当前线程上反映Realm的当前状态,包括在当前线程的写入事务期间。对此的一个例外是使用for...in枚举时,枚举开始时将始终枚举与查询匹配的对象,即便其中一些被删除或修改成在枚举期间被过滤器排除。

1 let puppies = realm.objects(Dog.self).filter("age < 2")
2 puppies.count // => 0
3 try! realm.write {
4     realm.create(Dog.self, value: ["name": "Fido", "age": 1])
5 }
6 puppies.count // => 1

这适用于全部Results:全部对象,已过滤和连接。

这种属性Results不只使Realm快速高效,并且使您的代码更简单,更具反应性。例如,若是视图控制器依赖于查询结果,则能够将其存储Results在属性中并对其进行访问,而无需确保在每次访问以前刷新其数据。

您能够订阅Realm通知,以了解Realm数据什么时候更新,指示应该刷新应用程序的UI的时间,而无需从新获取Results

因为结果是自动更新的,所以不要依赖索引和计数保持不变是很重要的。Results冻结的惟一时间是对其进行快速枚举,这样就能够在枚举对象时改变匹配查询的对象:

1 try! realm.write {
2     for person in realm.objects(Person.self).filter("age == 10") {
3         person.age += 1
4     }
5 }

或者,使用键值编码来执行操做Results

限制结果

大多数其余数据库技术提供了从查询中“分页”结果的能力(例如SQLite中的'LIMIT'关键字)。这一般是为了不从磁盘中读取太多内容,或者一次将太多结果拉入内存中。

因为Realm中的查询是惰性的,所以根本不须要执行这种分页行为,由于Realm只会在显式访问后从查询结果中加载对象。

若是出于UI相关或其余实现缘由,您须要查询中特定的对象子集,那么就像获取Results对象同样简单,只读取您须要的对象。

1 // Loop through the first 5 Dog objects
2 // restricting the number of objects read from disk
3 let dogs = try! Realm().objects(Dog.self)
4 for i in 0..<5 {
5     let dog = dogs[i]
6     // ...
7 }

迁移

使用任何数据库时,您的数据模型可能会随着时间的推移而发生变化。因为Realm中的数据模型被定义为标准的Swift类,所以进行模型更改就像更改任何其余Swift类同样简单。

假设咱们有如下Person模型:

1 class Person: Object {
2     @objc dynamic var firstName = ""
3     @objc dynamic var lastName = ""
4     @objc dynamic var age = 0
5 }

咱们但愿更新数据模型以要求fullName属性,而不是分隔名和姓。为此,咱们只需将对象界面更改成如下内容:

1 class Person: Object {
2     @objc dynamic var fullName = ""
3     @objc dynamic var age = 0
4 }

此时,若是您使用之前的型号版本保存了任何数据,则Realm在代码中定义的内容与Realm在磁盘上看到的数据之间将存在不匹配。发生这种状况时,除非您运行迁移,不然在尝试打开现有文件时将引起异常。

请注意,在迁移期间默认属性值不会应用于现有对象上的新对象或新属性。咱们认为这是一个错误,并将其跟踪为#1793

本地迁移

本地迁移由设置Realm.Configuration.schemaVersion定义Realm.Configuration.migrationBlock您的迁移块提供了将数据模型从先前模式转换为新模式的全部逻辑。Realm使用此配置建立a 时,若是须要迁移,将应用迁移块以更新Realm给定的架构版本。

假设咱们想要迁移Person先前声明模型。最小必要的迁移块将以下:

 1 // Inside your application(application:didFinishLaunchingWithOptions:)
 2 
 3 let config = Realm.Configuration(
 4     // Set the new schema version. This must be greater than the previously used
 5     // version (if you've never set a schema version before, the version is 0).
 6     schemaVersion: 1,
 7 
 8     // Set the block which will be called automatically when opening a Realm with
 9     // a schema version lower than the one set above
10     migrationBlock: { migration, oldSchemaVersion in
11         // We haven’t migrated anything yet, so oldSchemaVersion == 0
12         if (oldSchemaVersion < 1) {
13             // Nothing to do!
14             // Realm will automatically detect new properties and removed properties
15             // And will update the schema on disk automatically
16         }
17     })
18 
19 // Tell Realm to use this new configuration object for the default Realm
20 Realm.Configuration.defaultConfiguration = config
21 
22 // Now that we've told Realm how to handle the schema change, opening the file
23 // will automatically perform the migration
24 let realm = try! Realm()

咱们至少须要使用空块更新版本,以指示架构已由Realm升级(自动)。

更新值

虽然这是可接受的最小迁移,但咱们可能但愿使用此块来填充任何fullName有意义的新属性(在本例中)。在迁移块中,咱们能够调用Migration().enumerateObjects(ofType: _:_:)枚举Object某种类型的一种,并应用任何须要的迁移逻辑。请注意每一个枚举如何Object经过oldObject变量访问现有实例,并经过如下方式访问更新的实例newObject

 1 // Inside your application(application:didFinishLaunchingWithOptions:)
 2 
 3 Realm.Configuration.defaultConfiguration = Realm.Configuration(
 4     schemaVersion: 1,
 5     migrationBlock: { migration, oldSchemaVersion in
 6         if (oldSchemaVersion < 1) {
 7             // The enumerateObjects(ofType:_:) method iterates
 8             // over every Person object stored in the Realm file
 9             migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in
10                 // combine name fields into a single field
11                 let firstName = oldObject!["firstName"] as! String
12                 let lastName = oldObject!["lastName"] as! String
13                 newObject!["fullName"] = "\(firstName) \(lastName)"
14             }
15         }
16     })

迁移成功完成后,您的应用程序能够像往常同样访问Realm及其全部对象。

重命名属性

做为迁移的一部分在类上重命名属性比复制值和保留关系而不是复制它们更有效。

要在迁移期间重命名属性,请确保新模型具备具备新名称的属性,而且没有具备旧名称的属性。

若是新属性具备不一样的可为空性或索引设置,则将在重命名操做期间应用这些设置。

这里是你如何能够重命名PersonyearsSinceBirth属性age

 1 // Inside your application(application:didFinishLaunchingWithOptions:)
 2 
 3 Realm.Configuration.defaultConfiguration = Realm.Configuration(
 4     schemaVersion: 1,
 5     migrationBlock: { migration, oldSchemaVersion in
 6         // We haven’t migrated anything yet, so oldSchemaVersion == 0
 7         if (oldSchemaVersion < 1) {
 8             // The renaming operation should be done outside of calls to `enumerateObjects(ofType: _:)`.
 9             migration.renameProperty(onType: Person.className(), from: "yearsSinceBirth", to: "age")
10         }
11     })

线性迁移

假设咱们的应用程序有两个用户:JP和Tim。JP常常更新应用程序,但Tim刚好跳过了几个版本。JP可能已经看到了咱们应用程序的每一个新版本,而且按顺序升级了每一个架构:他下载了将他从v0带到v1的应用程序版本,以及后来从v1到v2的另外一个更新版本。相比之下,蒂姆可能会下载应用程序的更新,须要当即将他从v0带到v2。使用非嵌套 if (oldSchemaVersion < X)调用构建迁移块可确保它们将看到全部必需的升级,不管它们从哪一个架构版本开始。

对于跳过应用版本的用户,可能会出现另外一种状况。若是您删除email版本2 的属性并在版本3从新引入它,而且用户从版本1跳转到版本3,则Realm将没法自动检测到email属性的删除,由于它们之间不会存在不匹配磁盘上的架构以及该属性的代码中的架构。这将致使Tim的Person对象具备v3地址属性,该属性具备v1地址属性的内容。除非您在v1和v3之间更改了该属性的内部存储表示(例如,从ISO地址表示转到自定义表示),不然这可能不是问题。为避免这种状况,咱们建议您在email房产上取消房产if (oldSchemaVersion < 3) 声明,保证升级到版本3的全部Realms都具备正确的数据集。

通知

能够注册侦听器以接收有关Realm或其实体的更改的通知。当Realm做为一个总体被更改时发送领域通知更改,添加或删除单个对象时会发送收集通知

只要对返回的通知令牌进行引用,就会传递通知。您应该在注册更新的类上保留对此标记的强引用,由于在取消分配通知令牌时会自动取消注册通知。

通知始终在最初注册的线程上提供。该线程必须具备当前正在运行的运行循环若是您但愿在主线程之外的线程上注册通知,则您负责在该线程上配置和启动运行循环(若是尚不存在)。

在提交每一个相关的写事务以后异步调用通知处理程序,不管写事务发生在哪一个线程或进程上。

若是在启动写入事务时将Realm提高到最新版本,则可能会同步调用通知处理程序若是在Realm进入最新版本时,将以触发通知的方式修改或删除正在观察的Realm实体,则会发生这种状况。此类通知将在当前写入事务的上下文中运行,这意味着尝试在通知处理程序中开始写入事务将致使Realm抛出异常。若是您的应用程序的架构设置可能会出现这种状况,您可使用它Realm.isInWriteTransaction来肯定您是否已经在写入事务中。

因为使用运行循环传递通知,所以运行循环上的其余活动可能会延迟通知的传递。当没法当即传递通知时,多个写入事务的更改可能会合并为单个通知。

领域通知

通知处理程序能够在整个Realm上注册。每次提交涉及该Realm的写入事务时,不管写入事务发生在哪一个线程或进程上,都将触发通知处理程序:

1 // Observe Realm Notifications
2 let token = realm.observe { notification, realm in
3     viewController.updateUI()
4 }
5 
6 // later
7 token.invalidate()

收集通知

收集通知不会收到整个Realm,而是收到细粒度的更改说明。它们包括自上次通知以来已添加,删除或修改的对象索引。收集通知是异步传递的,首先是初始结果,而后是每次写入事务后再次发送,这会改变集合中的任何对象(或添加新对象)。

能够经过RealmCollectionChange传递给通知块参数访问这些更改这个对象保存有关受索引信息deletionsinsertionsmodifications

前两个,删除插入,在对象开始和中止成为集合的一部分时记录索引。这会将对象添加到Realm或从Realm中删除它们时考虑在内。为此,Results当您筛选特定值并更改对象以使其如今与查询匹配或再也不匹配时也适用。对于基于ListLinkingObjects包括派生的集合Results当在关系中添加或删除对象时,这也适用。

只要集合中对象的属性发生更改,您就会收到有关修改的通知这也发生更改的一对一一对多的关系,虽然通知不会采起反向关系考虑在内。

1 class Dog: Object {
2     @objc dynamic var name = ""
3     @objc dynamic var age = 0
4 }
5 
6 class Person: Object {
7     @objc dynamic var name = ""
8     let dogs = List<Dog>()
9 }

咱们假设您正在观察上面的模型代码给出的狗主人名单。在下列状况下,您将收到有关匹配Person对象的修改的通知

  • 你修改Personname属性。
  • 您添加或删除DogPersondogs财产。
  • 您修改属于age属性的属性DogPerson

这使得能够离散地控制对UI内容进行的动画和视觉更新,而不是每次发生通知时任意从新加载全部内容。

 1 class ViewController: UITableViewController {
 2     var notificationToken: NotificationToken? = nil
 3 
 4     override func viewDidLoad() {
 5         super.viewDidLoad()
 6         let realm = try! Realm()
 7         let results = realm.objects(Person.self).filter("age > 5")
 8 
 9         // Observe Results Notifications
10         notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
11             guard let tableView = self?.tableView else { return }
12             switch changes {
13             case .initial:
14                 // Results are now populated and can be accessed without blocking the UI
15                 tableView.reloadData()
16             case .update(_, let deletions, let insertions, let modifications):
17                 // Query results have changed, so apply them to the UITableView
18                 tableView.beginUpdates()
19                 tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
20                                      with: .automatic)
21                 tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
22                                      with: .automatic)
23                 tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
24                                      with: .automatic)
25                 tableView.endUpdates()
26             case .error(let error):
27                 // An error occurred while opening the Realm file on the background worker thread
28                 fatalError("\(error)")
29             }
30         }
31     }
32 
33     deinit {
34         notificationToken?.invalidate()
35     }
36 }

对象通知

Realm支持对象级通知。您能够在特定Realm对象上注册通知,以便在删除对象时或在对象上的任何托管属性修改其值时收到通知。(这也适用于将其值设置为其现有值的托管属性。)

只有Realm管理的对象可能在其上注册了通知处理程序。

对于在不一样线程或不一样进程中执行的写入事务,当管理对象的Realm(自动)刷新到包含更改的版本时,将调用该块,而对于本地写入事务,它将在某个时刻被调用。写入事务提交后的将来。

通知处理程序采用ObjectChange枚举值,该值指示对象是否已删除,对象上的属性值是否已更改,或者是否发生错误。ObjectChange.deleted若是删除了对象,将调用通知块永远不会再次调用该块。

ObjectChange.change若是对象的属性已更改则将调用该块枚举将包含一组PropertyChange值。这些值中的每个都包含已更改的属性的名称(做为字符串),前一个值和当前值。

若是发生错误,将调用该块并ObjectChange.error包含NSError一个错误。永远不会再次调用该块。

 1 class StepCounter: Object {
 2     @objc dynamic var steps = 0
 3 }
 4 
 5 let stepCounter = StepCounter()
 6 let realm = try! Realm()
 7 try! realm.write {
 8     realm.add(stepCounter)
 9 }
10 var token : NotificationToken?
11 token = stepCounter.observe { change in
12     switch change {
13     case .change(let properties):
14         for property in properties {
15             if property.name == "steps" && property.newValue as! Int > 1000 {
16                 print("Congratulations, you've exceeded 1000 steps.")
17                 token = nil
18             }
19         }
20     case .error(let error):
21         print("An error occurred: \(error)")
22     case .deleted:
23         print("The object was deleted.")
24     }
25 }

接口驱动的写入

Realm中的通知始终是异步传递的,所以它们永远不会阻止主UI线程,从而致使应用程序断断续续。可是,有些状况须要在主线程上同步完成更改,并当即反映在UI中。咱们将这些事务称为接口驱动的写入。

例如,假设用户将项添加到表视图中。理想状况下,UI应该为此操做设置动画,并在用户启动操做后当即启动此过程。

可是,当此插入的Realm更改通知稍后传递时,它将指示对象已添加到支持表视图的集合中,咱们将再次尝试在UI中插入新行。这种双重插入会致使UI和支持数据之间的状态不一致,从而致使应用程序崩溃!

执行接口驱动的写入时,传递通知块的通知令牌,这些通知块不该对第二次更改作出反应Realm.commitWrite(withoutNotifying:)

当使用带有同步Realm的细粒度收集通知时,此功能特别有用,由于之前考虑接口驱动写入的许多解决方法依赖于控制应用程序什么时候能够执行更改的完整状态。使用同步领域,只要它们被同步就会应用更改,这可能发生在应用程序生命周期的任什么时候候。

 1 // Add fine-grained notification block
 2 token = collection.observe { changes in
 3     switch changes {
 4     case .initial:
 5         tableView.reloadData()
 6     case .update(_, let deletions, let insertions, let modifications):
 7         // Query results have changed, so apply them to the UITableView
 8         tableView.beginUpdates()
 9         tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
10                              with: .automatic)
11         tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
12                              with: .automatic)
13         tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
14                              with: .automatic)
15         tableView.endUpdates()
16     case .error(let error):
17         // handle error
18         ()
19     }
20 }
21 
22 func insertItem() throws {
23      // Perform an interface-driven write on the main thread:
24      collection.realm!.beginWrite()
25      collection.insert(Item(), at: 0)
26      // And mirror it instantly in the UI
27      tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
28      // Making sure the change notification doesn't apply the change a second time
29      try collection.realm!.commitWrite(withoutNotifying: [token])
30 }

关键价值观察

领域对象是符合大多数属性的键值观察几乎全部Object子类上的托管(非忽略)属性都符合KVO,以及invalidated属性on ObjectListLinkingObjects使用KVO没法观察到属性。)

观察Object子类的非托管实例的属性就像使用任何其余动态属性同样,但请注意,realm.add(obj)当它具备任何已注册的观察者时,您没法将对象添加到Realm(使用或其余相似方法)。

观察托管对象(之前添加到Realm中的对象)的属性的工做方式略有不一样。对于托管对象,有三次属性值可能会发生变化:直接分配给它时; 当你realm.refresh()在另外一个线程上提交写入事务后调用或自动刷新域当你realm.beginWrite()在另外一个线程上的更改后调用时,当前线程上的刷新没有拾取这些更改。

在后两种状况下,将在另外一个线程上的写入事务中进行的全部更改将当即应用,而且KVO通知将一次所有发送。任何中间步骤都将被丢弃,所以若是在写入事务中将属性从1增长到10,则在主线程上,您将直接从1到10得到一次更改通知。因为属性在不在写入事务中或甚至在开始写入事务时可能会更改值,observeValueForKeyPath(_:ofObject:change:context:)所以不建议尝试从内部修改托管的Realm对象

NSMutableArray属性不一样,观察对List属性所作的更改不须要使用mutableArrayValueForKey(_:),尽管支持与不使用Realm编写的代码兼容。相反,您能够直接调用修改方法List,而且将通知任何观察其存储的属性的人。dynamic与普通属性不一样,列表属性不须要标记为可观察。

在咱们的例子应用,您能够找到使用领域具备很短的例子ReactiveCocoa从Objective-C中,并从斯威夫特ReactKit

加密

请注意咱们许可证的出口合规部分,由于若是您位于有美国出口限制或禁运的国家/地区,它会对使用Realm进行限制。

Realm支持在建立Realm时经过提供64字节加密密钥,使用AES-256 + SHA2加密磁盘上的数据库文件。

 1 // Generate a random encryption key
 2 var key = Data(count: 64)
 3 _ = key.withUnsafeMutableBytes { bytes in
 4     SecRandomCopyBytes(kSecRandomDefault, 64, bytes)
 5 }
 6 
 7 // Open the encrypted Realm file
 8 let config = Realm.Configuration(encryptionKey: key)
 9 do {
10     let realm = try Realm(configuration: config)
11     // Use the Realm as normal
12     let dogs = realm.objects(Dog.self).filter("name contains 'Fido'")
13 } catch let error as NSError {
14     // If the encryption key is wrong, `error` will say that it's an invalid database
15     fatalError("Error opening realm: \(error)")
16 }

这使得存储在磁盘上的全部数据均可以根据须要使用AES-256进行透明加密和解密,并使用SHA-2 HMAC进行验证。每次得到Realm实例时都必须提供相同的加密密钥。

请参阅咱们的加密示例应用程序,了解生成加密密钥的端到端应用程序,将其安全地存储在钥匙串中,并使用它来加密领域。

使用加密领域时,性能受到很小影响(一般低于10%)。

使用同步领域

您是否但愿使用Realm Mobile Platform同步全部Realm数据库?全部与同步相关的文档已移至咱们的平台文档中

穿线

领域读取事务生存期与Realm实例的内存生存期相关联避免经过使用自动刷新领域“固定”旧的Realm事务,并在显式自动释放池中包含全部使用Realm API的后台线程。

有关此效果的更多详细信息,请参阅咱们的当前限制

在单个线程中,您能够将全部内容视为常规对象,而无需担忧并发或多线程。不须要任何锁定或资源协调来访问它们(即便它们同时在其余线程上被修改),而且它只修改必须包含在写入事务中的操做。

经过确保每一个线程始终具备一致的Realm视图,Realm使并发使用变得容易。您能够在同一个Realms上并行处理任意数量的线程,而且由于它们都有本身的快照,因此它们永远不会致使彼此看到不一致的状态。

您惟一须要注意的是,您不能让多个线程共享相同的Realm对象实例若是多个线程须要访问相同的对象,则每一个线程都须要获取本身的实例(不然在一个线程上发生的更改可能会致使其余线程看到不完整或不一致的数据)。

查看其余线程的更改

在主UI线程(或任何具备runloop的线程)上,对象将在runloop的每次迭代之间自动更新来自其余线程的更改。在任何其余时间,您将处理快照,所以各个方法始终能够看到一致的视图,而没必要担忧其余线程上发生的状况。

当您最初在线程上打开Realm时,其状态将基于最近成功的写入提交,而且它将保留在该版本上直到刷新。除非将Realm的autorefresh属性设置为,不然领域会在每次runloop迭代开始时自动刷新NO若是一个线程没有runloop(后台线程一般就是这种状况),那么Realm.refresh()必须手动调用,以便将事务推动到最近的状态。

提交写入事务时,域也会刷新(Realm.commitWrite())。

未能按期刷新Realms可能致使某些事务版本变为“固定”,从而阻止Realm重用该版本使用的磁盘空间,从而致使更大的文件大小。

跨线程传递实例

Objects的非托管实例与常规NSObject子类彻底相同,而且能够安全地传递线程。

的实例RealmResults或者List,托管实例或者Object线程限制,这意味着它们只能在建立它们的线程上使用,不然会抛出异常*。这是Realm强制执行事务版本隔离的一种方式。不然,当在没有可能普遍的关系图的状况下在不一样事务版本的线程之间传递对象时,将没法肯定应该作什么。

Realm公开了一种机制,能够经过三个步骤安全地传递线程限制的实例:

  1. 使用ThreadSafeReference线程限制对象初始化a 
  2. 将其传递ThreadSafeReference到目标线程或队列。
  3. 经过调用在目标Realm上解析此引用Realm.resolve(_:)像往常同样使用返回的对象。
 1 let person = Person(name: "Jane")
 2 try! realm.write {
 3     realm.add(person)
 4 }
 5 let personRef = ThreadSafeReference(to: person)
 6 DispatchQueue(label: "background").async {
 7     autoreleasepool {
 8         let realm = try! Realm()
 9         guard let person = realm.resolve(personRef) else {
10             return // person was deleted
11         }
12         try! realm.write {
13             person.name = "Jane Doe"
14         }
15     }
16 }

一个ThreadSafeReference对象必须最多一次能够解决。未能解析ThreadSafeReference将致使Realm的源版本被固定,直到引用被取消分配。出于这个缘由,ThreadSafeReference应该是短暂的。

能够从任何线程访问这些类型的一些属性和方法:

  • Realm:全部属性,类方法和初始化程序。
  • ObjectisInvalidatedobjectSchemarealm,类方法,并初始化。
  • ResultsobjectClassNamerealm
  • ListisInvalidatedobjectClassName,和realm

跨线程使用领域

要从不一样的线程访问同一个Realm文件,您必须初始化一个新的Realm,以便为您的应用程序的每一个线程获取不一样的实例。只要指定相同的配置,全部Realm实例都将映射到磁盘上的同一文件。

支持跨线程共享Realm实例访问同一Realm文件的Realm实例也必须所有使用相同的Realm实例Realm.Configuration

经过在单个事务中将多个突变批处理在一块儿编写大量数据时,域能够很是高效。也可使用Grand Central Dispatch在后台执行事务,以免阻塞主线程。Realm对象不是线程安全的,不能跨线程共享,所以您必须在要读取或写入的每一个线程/调度队列中获取Realm实例。如下是在后台队列中插入一百万个对象的示例:

 1 DispatchQueue(label: "background").async {
 2     autoreleasepool {
 3         // Get realm and table instances for this thread
 4         let realm = try! Realm()
 5 
 6         // Break up the writing blocks into smaller portions
 7         // by starting a new transaction
 8         for idx1 in 0..<1000 {
 9             realm.beginWrite()
10 
11             // Add row via dictionary. Property order is ignored.
12             for idx2 in 0..<1000 {
13                 realm.create(Person.self, value: [
14                     "name": "\(idx1)",
15                     "birthdate": Date(timeIntervalSince1970: TimeInterval(idx2))
16                 ])
17             }
18 
19             // Commit the write transaction
20             // to make this data available to other threads
21             try! realm.commitWrite()
22         }
23     }
24 }

JSON

Realm没有直接支持JSON,可是能够Object使用输出来从JSON 添加NSJSONSerialization.JSONObjectWithData(_:options:)生成的符合KVC的对象可用于Object使用标准API添加/更新以建立和更新对象。

 1 // A Realm Object that represents a city
 2 class City: Object {
 3     @objc dynamic var city = ""
 4     @objc dynamic var id = 0
 5     // other properties left out ...
 6 }
 7 
 8 let data = "{\"name\": \"San Francisco\", \"cityId\": 123}".data(using: .utf8)!
 9 let realm = try! Realm()
10 
11 // Insert from Data containing JSON
12 try! realm.write {
13     let json = try! JSONSerialization.jsonObject(with: data, options: [])
14     realm.create(City.self, value: json, update: .modified)
15 }

若是JSON中有嵌套对象或数组,它们将自动映射到一对多关系。有关更多详细信息,请参阅嵌套对象部分。

使用此方法在Realm中插入或更新JSON数据时,请注意Realm指望JSON属性名称和类型与Object属性彻底匹配。例如:

  • float应使用float-backed 初始化属性NSNumbers
  • DateData属性不能从字符串自动推断,但应在传递以前转换为适当的类型Realm().create(_:value:update:)
  • 若是为必需属性提供了JSON null(即NSNull),则将引起异常。
  • 若是在插入时没有为必需属性提供属性,则将引起异常。
  • Realm将忽略未定义的JSON中的任何属性Object

若是您的JSON架构与Realm对象不彻底对齐,咱们建议您使用第三方模型映射框架来转换您的JSON。Swift有一组蓬勃发展的主动维护模型映射框架,它与Realm一块儿工做,其中一些列在realm-cocoa存储库中

测试和调试

配置默认域

使用和测试Realm应用程序的最简单方法是使用默认的Realm为了不在测试之间覆盖应用程序数据或泄漏状态,您只需将默认Realm设置为每一个测试的新文件。

 1 import XCTest
 2 
 3 // A base class which each of your Realm-using tests should inherit from rather
 4 // than directly from XCTestCase
 5 class TestCaseBase: XCTestCase {
 6     override func setUp() {
 7         super.setUp()
 8 
 9         // Use an in-memory Realm identified by the name of the current test.
10         // This ensures that each test can't accidentally access or modify the data
11         // from other tests or the application itself, and because they're in-memory,
12         // there's nothing that needs to be cleaned up.
13         Realm.Configuration.defaultConfiguration.inMemoryIdentifier = self.name
14     }
15 }

注入Realm实例

测试与Realm相关的代码的另外一种方法是让您要测试的全部方法都接受一个Realm实例做为参数,这样您就能够在运行应用程序和测试时传入不一样的Realms。例如,假设您的应用程序具备GET来自JSON API的用户配置文件的方法,而且您但愿测试是否正确建立了本地配置文件:

 1 // Application Code
 2 func updateUserFromServer() {
 3     let url = URL(string: "http://myapi.example.com/user")
 4     URLSession.shared.dataTask(with: url!) { data, _, _ in
 5         let realm = try! Realm()
 6         createOrUpdateUser(in: realm, with: data!)
 7     }
 8 }
 9 
10 public func createOrUpdateUser(in realm: Realm, with data: Data) {
11     let object = try! JSONSerialization.jsonObject(with: data) as! [String: String]
12     try! realm.write {
13         realm.create(User.self, value: object, update: .modified)
14     }
15 }
16 
17 // Test Code
18 
19 let testRealmURL = URL(fileURLWithPath: "...")
20 
21 func testThatUserIsUpdatedFromServer() {
22     let config = Realm.Configuration(fileURL: testRealmURL)
23     let testRealm = try! Realm(configuration: config)
24     let jsonData = "{\"email\": \"help@realm.io\"}".data(using: .utf8)!
25     createOrUpdateUser(in: testRealm, with: jsonData)
26     let expectedUser = User()
27     expectedUser.email = "help@realm.io"
28     XCTAssertEqual(testRealm.objects(User.self).first!, expectedUser,
29                    "User was not properly updated from server.")
30 }

调试

Realm Studio

Realm Studio是咱们的首选开发人员工具,能够轻松管理Realm数据库和Realm平台。使用Realm Studio,您能够打开和编辑本地和同步的域,并管理任何Realm Object Server实例。它支持Mac,Windows和Linux。

Realm Studio

使用Realm的Swift API调试应用程序必须经过LLDB控制台完成。

请注意,虽然LLDB脚本容许在Xcode的UI中检查Realm变量的内容,但这对Swift来讲还不起做用。相反,这些变量将显示不正确的数据。您应该使用LLDB的po命令来检查存储在Realm中的数据的内容。

因为您使用Realm做为动态框架,所以您须要确保您的单元测试目标能够找到Realm。您能够经过将父路径添加RealmSwift.framework到单元测试的“框架搜索路径”来完成此操做。

若是您的测试失败并显示异常消息"Object type 'YourObject' is not managed by the Realm",则多是由于您已将Realm框架直接连接到测试目标,这不该该完成。将Realm与测试目标断开链接应解决这个问题。

您还应确保仅在应用程序或框架目标中编译模型类文件; 永远不要将它们添加到您的单元测试目标 不然,在测试时将复制这些类,这可能致使难以调试的问题(有关详细信息,请参阅此问题)。

您须要确保测试所需的全部代码都暴露给您的单元测试目标(使用public访问修饰符或@testable)。有关详细信息,请参阅此Stack Overflow答案。

目前的局限

这是咱们最多见的限制列表。

有关已知问题的更全面列表,请参阅咱们的GitHub问题。

通常

Realm旨在在灵活性和性能之间取得平衡。为了实现这一目标,对在Realm中存储信息的各个方面施加了现实限制。例如:

  1. 类名最多限制为57个UTF8字符。
  2. 属性名称限制为最多63个UTF8字符。
  3. DataString属性不能容纳超过16MB的数据。要存储大量数据,请将其分解为16MB块或将其直接存储在文件系统中,并在Realm中存储这些文件的路径。若是您的应用尝试在单个属性中存储超过16MB,则会在运行时抛出异常。
  4. 任何单个Realm文件都不能大于容许应用程序在iOS中映射的内存量 - 这会改变每一个设备,并取决于该时间点内存空间的碎片程度(关于此问题的雷达是开放的) :rdar:// 17119975)。若是须要存储更多数据,能够将其映射到多个Realm文件。
  5. 字符串排序和不区分大小写的查询仅支持“Latin Basic”,“Latin Supplement”,“Latin Extended A”,“Latin Extended B”(UTF-8范围0-591)中的字符集。

主题

尽管Realm文件能够由多个线程同时访问,但您没法直接在线程之间传递Realms,Realm对象,查询和结果。若是须要在线程之间传递Realm对象,可使用ThreadSafeReferenceAPI。阅读有关Realm线程的更多信息。

楷模

Setter和getter:因为Realm会覆盖setter和getter直接由底层数据库返回属性,所以不能在对象上覆盖它们。一个简单的解决方法是建立新的,Realm忽略的属性,能够覆盖其访问,并能够调用其余setter / getter。

自动递增属性:在生成主键时,Realm没有用于其余数据库中经常使用的线程安全/进程安全自动递增属性的机制。可是,在须要惟一自动生成值的大多数状况下,没必要具备连续的,连续的整数ID。惟一的字符串主键一般就足够了。常见的模式是将默认属性值设置NSUUID().UUIDString为生成惟一的字符串ID。

自动递增属性的另外一个常见动机是保持插入顺序。在某些状况下,这能够经过将对象附加到a List或使用createdAt默认值为的属性来实现Date()

从Objective-C的属性:若是您须要从Objective-C的访问你的境界雨燕车型,ListRealmOptional属性将致使自动生成的Objective-C头(-Swift.h)失败,由于使用泛型的编译。您能够经过注释属性来解决这个已知的Swift错误,这会将它们隐藏在自动生成的Objective-C标头()中。ListRealmOptional@nonobjc-Swift.h

Object子类的自定义初始值设定项:建立模型Object子类时,有时可能须要添加本身的自定义初始化方法以增长方便性。

因为Swift内省存在一些限制,这些方法不能被指定为类的初始化器。相反,它们须要使用相同名称的Swift关键字标记为便利初始化器:

1 class MyModel: Object {
2     @objc dynamic var myValue = ""
3 
4     convenience init(myValue: String) {
5         self.init() //Please note this says 'self' and not 'super'
6         self.myValue = myValue
7     }
8 }

文件大小

领域读取事务生存期与Realm实例的内存生存期相关联避免经过使用自动刷新领域“固定”旧的Realm事务,并在显式自动释放池中包含全部使用Realm API的后台线程。

您应该指望Realm数据库在磁盘上占用的空间少于等效的SQLite数据库。若是您的Realm文件比预期的要大得多,多是由于您有一个Realm指的是数据库中较旧版本的数据。

为了给您一致的数据视图,Realm只更新在运行循环迭代开始时访问的活动版本。这意味着若是您从Realm读取一些数据,而后在其余线程上写入Realm时在长时间运行的操做中阻塞该线程,则该版本永远不会更新,而且Realm必须保留您的数据的中间版本可能实际上并不须要,致使每次写入时文件大小增长。额外的空间最终将被将来的写入重用,或者可能被压缩 - 例如,经过设置shouldCompactOnLaunch或调用Realm().writeCopyToPath(_:encryptionKey:)为避免此问题,您能够致电invalidate告诉Realm您再也不须要到目前为止从Realm中读取的任何对象,这使咱们没法跟踪这些对象的中间版本。Realm将在下次访问时更新到最新版本。

使用Grand Central Dispatch访问Realm时,您可能也会看到此问题。当一个Realm在调度队列的自动释放池中结束时会发生这种状况,由于这些池在执行代码后可能不会耗尽一段时间。Realm取消分配对象以前,不能重用Realm文件中的数据的中间版本要避免此问题,从分派队列访问Realm时应使用显式自动释放池。

使用Realm API初始化Swift属性

您的Swift应用程序的类和结构可能使用其值使用Realm API初始化的属性进行定义。例如:

1 class SomeSwiftType {
2     let persons = try! Realm().objects(Person.self)
3     // ...
4 }

若是您确实定义了具备此类属性的类型,则应注意,若是在完成Realm配置的设置以前调用此类初始化代码,则可能会遇到问题。例如,若是您为默认的Realm配置设置了一个迁移块applicationDidFinishLaunching(),可是您建立了一个SomeSwiftTypebefore applicationDidFinishLaunching()run 实例而且您的Realm须要迁移,那么您将在正确配置以前访问您的Realm。

为了不此类问题,您能够选择:

  1. 在您的应用程序完成其Realm配置设置以后,推迟使用Realm API急切初始化属性的任何类型的实例化。
  2. 使用Swift的lazy关键字定义属性这容许您在应用程序的生命周期中随时安全地实例化此类类型,只要您的应用程序设置其Realm配置以后才尝试访问您的惰性属性。
  3. 仅使用明确接受用户定义配置的Realm API初始化您的属性。这样,您能够确保在使用配置值打开Realms以前已正确设置它们。

加密领域和多个进程

多个进程没法同时访问加密域。这包括iOS扩展程序。要解决此问题,请使用未加密的域,这些域能够跨进程共享。您可使用Security和CommonCrypto系统框架来加密和解密存储在NSDataRealm对象上的属性中的数据

咱们正在追踪Realm Cocoa问题跟踪器(#1693)和Realm Core问题跟踪器(#1845)中的这一限制

食谱

咱们已经汇总了一些显示如何使用Realm来完成一些特定任务的方法。咱们会按期添加更多食谱,所以请常常查看。若是您想看一个例子,请在GitHub上打开一个问题

常问问题

如何查找和查看个人Realm文件的内容?

这个SO问题描述了在哪里找到您的Realm文件。而后,您可使用咱们的Realm Studio查看内容

Realm基础库有多大?

Realm应该只为你的应用程序的下载大小增长大约5到8 MB。咱们发布的版本要大得多,由于它们包括对iOS,watchOS和tvOS模拟器,一些调试符号和bitcode的支持,全部这些都会在下载应用程序时自动被App Store剥离。

Realm开源吗?

是! Realm的内部C ++存储引擎及其上的语言SDK彻底是开源的,并在Apache 2.0下得到许可。Realm还可选择包含闭源同步组件,但不须要将Realm用做嵌入式数据库。

我在运行应用程序时看到了对Mixpanel的网络调用

当您的应用程序在附加调试器的状况下运行或在模拟器中运行时,Realm会收集匿名分析。这些分析彻底是匿名的,能够经过标记Realm,iOS,macOS的哪一个版本或您定位的语言以及咱们能够弃用的版原本帮助咱们改进产品。当您的应用程序正在生产中,或在您的用户设备上运行时,此调用不会在您的模拟器内部或附加调试器时运行。您能够在咱们的源代码中看到咱们收集的内容以及咱们如何收集它们以及这样作的理由

为何Realm不支持Swift结构做为模型?

Realm目前不支持结构做为模型的缘由有不少。

最重要的是,Realm是围绕“实时”对象设计的,这个概念从根本上与值类型结构不兼容。Realm提供了许多与这些语义不兼容的功能:数据的活跃性,API的反应性,数据的低内存占用,操做性能,对部分数据的懒惰和廉价访问,缺少数据序列化/反序列化,保持可能复杂的对象图同步等

话虽如此,从对象Realm中分离对象有时颇有用。不幸的是,这一般须要解决咱们库中的临时限制,而不是理想的设计决策(例如线程限制)。这就是为何咱们努力确保独立/分离的Realm对象的行为与普通的旧NSObject彻底相同。咱们支持经过暴露经过KVC复制持久化对象属性建立独立对象的初始化程序来制做Realm对象的“内存中副本”。例如:

let standaloneModelObject = MyModel(value: persistedModelObject)

故障排除

崩溃报告

咱们鼓励您在应用程序中使用崩溃报告器。许多Realm操做可能在运行时失败(与任何其余磁盘I / O同样),所以从应用程序收集崩溃报告将有助于肯定您(或咱们)能够改进错误处理和修复崩溃错误的区域。

大多数商业崩溃记者均可以选择收集日志。咱们强烈建议您启用此功能。在抛出异常和不可恢复的状况时,Realm会记录元数据信息(但没有用户数据),这些消息能够在出现问题时帮助调试。

报告领域问题

若是您发现Realm存在问题,请在GitHub上提交问题或发送电子邮件至help@realm.io,尽量多地了解咱们以了解并重现您的问题。

如下信息对咱们很是有用:

  1. 目标。
  2. 预期成绩。
  3. 实际结果。
  4. 重现步骤。
  5. 突出问题的代码示例(咱们能够编译的完整Xcode项目是理想的)
  6. Realm / Xcode / macOS的版本。
  7. 涉及的依赖管理器的版本(CocoaPods / Carthage)。
  8. 发生错误的平台,操做系统版本和体系结构(例如64位iOS 8.1)。
  9. 崩溃日志和堆栈跟踪。有关详情,请参阅上面的崩溃报告

依赖管理者

若是您经过CocoaPods或Carthage安装了Realm而且遇到了构建错误,那么您可能正在使用该受支持管理器的不受支持的版本,Realm与项目的集成未成功,或者您的构建的一部分工具备过期的缓存。若是是这种状况,请尝试删除依赖关系管理器建立的文件夹并从新安装。

您还能够尝试删除派生数据清除Xcode中的构建文件夹 ; 这能够解决更新构建工具版本或更改项目设置(例如添加新目标,共享目标之间的依赖关系等)所致使的问题。

要清理构建文件夹,请在打开“产品”菜单时按住“选项”键,而后选择“清除构建文件夹...”。您还能够在Xcode帮助搜索菜单中键入“清理”,并在搜索结果中显示时选择“清洁构建文件夹...”菜单项。

的CocoaPods

能够经过CocoaPods 0.39.0或更高版本安装Realm。

若是您的CocoaPods集成存在问题,则可能有助于重置集成状态。要实现这一点,只需在项目目录中的Terminal中运行如下命令:

1 pod cache clean Realm
2 pod cache clean RealmSwift
3 pod deintegrate || rm -rf Pods
4 pod install --verbose
5 rm -rf ~/Library/Developer/Xcode/DerivedData

您也可使用cocoapods-deintegrate而不是删除Pods目录。使用CocoaPods 1.0,这是预装的插件。若是您使用的是旧版本,则能够考虑安装它gem install cocoapods-deintegrate你能够运行它pod deintegrate这将从Xcode项目中删除全部CocoaPods的痕迹。

迦太基

能够经过Carthage 0.9.2或更高版本安装Realm。

要从项目中删除全部Carthage管理的依赖项,只需在项目目录的Terminal中运行如下命令:

1 rm -rf Carthage
2 rm -rf ~/Library/Developer/Xcode/DerivedData
3 carthage update

Realm Core二进制文件没法下载

在构建Realm时,该过程的一部分包括将核心库做为静态二进制文件下载并将其集成到realm-cocoa项目中。据报道,在某些状况下,核心二进制文件没法下载,并出现如下错误:

Downloading core failed. Please try again once you have an Internet connection.

因为如下任何缘由可能会发生此错误:

  1. 您的IP地址范围来自美国禁运列表中的区域为了遵照美国法律,还没有在该地区提供Realm。有关更多信息,请参阅咱们的许可证
  2. 您位于中国大陆,因为全国范围的防火墙目前没法正常访问CloudFlare或Amazon AWS S3服务。有关更多信息,请参阅此Realm-Cocoa问题
  3. Amazon AWS S3可能遇到服务问题。请查看AWS Service Health仪表板,稍后再试。

以低内存限制运行

若是您想在具备少许可用内存的上下文中使用Realm,例如watchOS应用程序或App Extension,咱们建议您明确指定要由Realm管理的类,以免代价高昂的调用objc_copyClassList()

1 let config = Realm.Configuration(objectTypes: [Dog.self, Person.self])
2 let realm = try! Realm(configuration: config)
相关文章
相关标签/搜索