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)
相關文章
相關標籤/搜索