Realm Objective-C

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公衆號:山青詠芝(shanqingyongzhi)
➤博客園地址:山青詠芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:http://www.javashuo.com/article/p-bdazlfoq-bx.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中,添加pod 'Realm'到您的應用目標和pod 'Realm/Headers'測試目標。
  4. 從命令行運行pod install
  5. 使用.xcworkspaceCocoaPods生成文件來處理您的項目!
  6. 若是將Realm與Swift一塊兒使用,請將文件拖到Swift/RLMSupport.swiftXcode項目的File Navigator中,選中Copy items if if needed複選框。

入門

若是您但願純粹使用來自Swift的Realm,請考慮使用Realm SwiftRealm Objective-C和Realm Swift API不可互操做,不支持它們一塊兒使用。git

Realm Objective-C使您可以以安全,持久和快速的方式有效地編寫應用程序的模型層。這是它的樣子:github

// Define your models like regular Objective‑C classes @interface Dog : RLMObject @property NSString *name; @property NSData *picture; @property NSInteger age; @end @implementation Dog @end RLM_ARRAY_TYPE(Dog) @interface Person : RLMObject @property NSString *name; @property RLMArray<Dog *><Dog> *dogs; @end @implementation Person @end // Use them like regular Objective‑C objects Dog *mydog = [[Dog alloc] init]; mydog.name = @"Rex"; mydog.age = 1; mydog.picture = nil; // properties are nullable NSLog(@"Name of dog: %@", mydog.name); // Query Realm for all dogs less than 2 years old RLMResults<Dog *> *puppies = [Dog objectsWhere:@"age < 2"]; puppies.count; // => 0 because no dogs have been added to the Realm yet // Persist your data easily RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject:mydog]; }]; // Queries are updated in realtime puppies.count; // => 1 // Query and update the result in another thread dispatch_async(dispatch_queue_create("background", 0), ^{ @autoreleasepool { Dog *theDog = [[Dog objectsWhere:@"age == 1"] firstObject]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; theDog.age = 3; [realm commitWriteTransaction]; } });

Realm Studio

Realm Studio是咱們的首選開發人員工具,能夠輕鬆管理Realm數據庫和Realm平臺。使用Realm Studio,您能夠打開和編輯本地和同步的域,並管理任何Realm Object Server實例。它支持Mac,Windows和Linux。objective-c

Realm Studio

使用菜單項「 工具」>「生成演示數據庫」建立包含示例數據的測試數據庫swift

若是您在查找應用程序的Realm文件時須要幫助,請查看此StackOverflow答案以獲取詳細說明。

例子

您能夠在咱們的發佈zip找到iOS和OS X的示例應用程序examples/,演示如何使用Realm的許多功能,如遷移,如何使用它UITableViewController,加密,命令行工具等等。

使用Realm框架

在Objective-C源文件的頂部,用於#import <Realm/Realm.h>導入Realm Objective-C並使其可用於您的代碼。在Swift源文件的頂部(若是有的話),使用import Realm這就是你開始所須要的一切!

使用Swift的Realm Objective-C

若是你想從Swift純粹使用Realm ,你應該考慮使用Realm Swift

Realm Objective-C旨在與混合的Objective-C和Swift項目一塊兒使用。從Swift開始,您能夠在使用Objective-C中的Realm時執行全部操做,例如定義模型和使用Realm的Objective-C API。可是,您應該作的一些事情與純Objective-C項目略有不一樣:

RLMSupport.swift

咱們建議您編譯Swift / RLMSupport.swift文件(也能夠在咱們的發行版zip中找到)。此文件添加了Sequence對Realm Objective-C集合類型的一致性,並從新公開了Swift自己沒法訪問的Objective-C方法,包括可變參數。

Realm Objective-C默認不包含此文件,由於這會強制Realm Objective-C的全部用戶包含大量的Swift動態庫,不管他們是否在他們的應用程序中使用Swift!

RLMArray屬性

在Objective-C中,咱們依靠協議一致性使Realm知道在RLMArray 多對多關係中包含的對象類型在Swift中,這種語法是不可能的。所以,您應該RLMArray使用如下語法聲明屬性:

class Person: Object { @objc dynamic var dogs = RLMArray(objectClassName: Dog.className()) }

這至關於Objective-C中的如下內容:

@interface Person : RLMObject @property RLMArray<Dog *><Dog> *dogs; @end

tvOS

由於在tvOS上禁止寫入「Documents」目錄,因此默認的Realm位置設置爲NSCachesDirectory可是,請注意tvOS能夠隨時清除「Caches」目錄中的文件,所以咱們建議您依賴Realm做爲可重建的緩存,而不是存儲重要的用戶數據。

若是您想在tvOS應用程序和電視服務擴展(例如Top Shelf擴展)之間共享Realm文件,則必須使用Library/Caches/共享容器中的應用程序組目錄。

// end declarations RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.fileURL = [[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.io.realm.examples.extension"] URLByAppendingPathComponent:@"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文件,不管其建立時間如何。

RLMRealm *realm = [RLMRealm defaultRealm]; // Get our Realm file's parent directory NSString *folderPath = realm.configuration.fileURL.URLByDeletingLastPathComponent.path; // Disable file protection for this directory [[NSFileManager defaultManager] setAttributes:@{NSFileProtectionKey: NSFileProtectionNone} ofItemAtPath:folderPath error:nil];

三界

一個境界是一種境界移動數據庫容器的一個實例。

有關Realms的詳細討論,請閱讀The Realm Data Model有關建立和管理領域的信息,請參閱

打開本地領域

要打開Realm,請實例化一個新RLMRealm對象:

RLMRealm *realm = [RLMRealm defaultRealm]; [realm transactionWithBlock:^{ [realm addObject:mydog]; }];

這會實例化默認的Realm

配置本地領域

經過建立實例RLMRealmConfiguration並設置適當的屬性,在打開Realm以前配置它建立和自定義配置值容許您自定義以及其餘方面:

  • 本地Realm文件位置的路徑
  • 遷移功能,若是一個領域的模式和版本之間的更改必須更新
  • 配置壓縮功能以確保有效利用磁盤空間。

能夠在+[RLMRealm realmWithConfiguration:config error:&err]每次須要Realm實例時傳遞配置,也能夠將配置設置爲默認Realm實例[RLMRealmConfiguration setDefaultConfiguration:config]

例如,假設您有一個應用程序,用戶必須登陸到您的Web後端,而且您但願支持在賬戶之間快速切換。您能夠經過執行如下操做爲每一個賬戶提供本身的Realm文件,該文件將用做默認Realm:

@implementation SomeClass + (void)setDefaultRealmForUser:(NSString *)username { RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; // Use the default directory, but replace the filename with the username config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent] URLByAppendingPathComponent:username] URLByAppendingPathExtension:@"realm"]; // Set this as the configuration used for the default Realm [RLMRealmConfiguration setDefaultConfiguration:config]; } @end

您能夠擁有多個配置對象,所以您能夠獨立控制每一個Realm的版本,架構和位置。

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; // Get the URL to the bundled file config.fileURL = [[NSBundle mainBundle] URLForResource:@"MyBundledData" withExtension:@"realm"]; // Open the file in read-only mode as application bundles are not writeable config.readOnly = YES; // Open the Realm with the configuration RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil]; // Read some data from the bundled Realm RLMResults<Dog *> *dogs = [Dog objectsInRealm:realm where:@"age > 5"];

存儲可寫Realm文件的最多見位置是iOS上的「Documents」目錄和macOS上的「Application Support」目錄。請尊重Apple的iOS數據存儲指南,該指南建議若是應用程序能夠從新生成的文檔應存儲在<Application_Home>/Library/Caches目錄中。若是使用自定義URL初始化Realm,則必須描述具備寫入權限的位置。

默認領域

到目前爲止,您可能已經注意到咱們realm經過調用初始化了對變量的訪問[RLMRealm defaultRealm]該方法返回一個RLMRealm對象,對象映射到default.realm應用程序的Documents文件夾(iOS)或Application Support文件夾(macOS)中指定的文件。

Realm API中的許多方法都有一個接受RLMRealm實例的版本,以及一個使用默認Realm的便捷版本。例如,[RLMObject allObjects]至關於[RLMObject allObjectsInRealm:[RLMRealm defaultRealm]]

請注意,默認的Realm構造函數和默認的Realm便捷方法不容許錯誤處理; 你應該只在初始化Realm時使用它們不能失敗。有關詳細信息,請參閱錯誤處理文檔

打開同步領域

您是否但願使用Realm Mobile Platform同步全部Realm數據庫?全部與同步相關的文檔已移至咱們的平臺文檔中

內存領域

經過設置inMemoryIdentifier而不是fileURLon RLMRealmConfiguration,您能夠建立一個徹底在內存中運行而不會持久保存到磁盤的Realm。設置inMemoryIdentifier將爲零fileURL(反之亦然)。

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.inMemoryIdentifier = @"MyInMemoryRealm"; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];

內存領域不會跨應用程序啓動保存數據,但Realm的全部其餘功能將按預期工做,包括查詢,關係和線程安全。若是您須要靈活的數據訪問而沒有磁盤持久性的開銷,這是一個有用的選項。

內存領域在臨時目錄中建立多個文件,用於協調跨進程通知等事務。實際上沒有數據寫入文件,除非因爲內存壓力操做系統須要交換到磁盤。

注意:當具備特定標識符的全部內存中Realm實例超出範圍而沒有引用時,該Realm中的全部數據都將被刪除。咱們建議您在應用程序的生命週期內保留對任何內存領域的強引用。(對於磁盤領域,這不是必需的。)

錯誤處理

與任何磁盤I / O操做同樣,RLMRealm若是資源受到限制,建立實例有時可能會失敗。實際上,這隻能在第一次在給定線程上建立Realm實例時發生。從同一個線程對Realm的後續訪問將重用高速緩存的實例並始終成功。

要在首次訪問給定線程上的Realm時處理錯誤,請提供NSError指向該error參數指針

NSError *error = nil; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error]; if (!realm) { // handle error }

輔助領域文件

除標準.realm文件外,Realm還爲其本身的內部操做生成並維護其餘文件和目錄。

  • .realm.lock - 資源鎖的鎖文件。
  • .realm.management - 進程間鎖定文件的目錄。
  • .realm.note - 用於通知的命名管道。

這些文件對.realm數據庫文件沒有任何影響,若是刪除或替換父數據庫文件,則不會致使任何錯誤行爲。

報告領域的問題,請必定要包括這些輔助文件與主一塊兒.realm的文件,由於它們包含用於調試的信息。

捆綁一個境界

一般使用初始數據爲應用程序設定種子,使其在首次啓動時當即可供您的用戶使用。這是如何作到這一點:

  1. 首先,填充領域。您應該使用與最終發貨應用相同的數據模型來建立Realm,並使用您但願與應用捆綁在一塊兒的數據填充它。因爲Realm文件是跨平臺的,您可使用macOS應用程序(請參閱咱們的JSONImport示例)或在模擬器中運行的iOS應用程序。
  2. 在您生成此Realm文件的代碼中,您應該經過製做文件的壓縮副原本完成(請參閱參考資料-[RLMRealm writeCopyToPath:error:])。這將減小Realm的文件大小,使您的最終應用程序更輕鬆地爲您的用戶下載。
  3. 將Realm文件的新壓縮副本拖到最終應用程序的Xcode Project Navigator中。
  4. 轉到Xcode中的app target的構建階段選項卡,並將Realm文件添加到「Copy Bundle Resources」構建階段。
  5. 此時,您的應用能夠訪問捆綁的Realm文件。您可使用找到它的路徑[[NSBundle mainBundle] pathForResource:ofType:]
  6. 若是捆綁的領域包含您不須要修改固定的數據,你能夠直接從束路徑設置中打開它readOnly = true的上RLMRealmConfiguration對象。不然,若是它是您要修改的初始數據,則可使用將捆綁的文件複製到應用程序的Documents目錄中[[NSFileManager defaultManager] copyItemAtPath:toPath:error:]

您能夠參考咱們的遷移示例應用程序,以獲取有關如何使用捆綁的Realm文件的示例。

類子集

在某些狀況下,您可能但願限制哪些類能夠存儲在特定領域中。例如,若是您有兩個團隊在應用程序的不一樣組件上工做,這兩個組件都在內部使用Realm,那麼您可能不但願必須協調它們之間的遷移你能夠經過設置objectClasses屬性來作到這一點RLMRealmConfiguration

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[MyClass.class, MyOtherClass.class]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];

壓縮領域

Realm的工做方式是Realm文件的大小始終大於存儲在其中的對象的總大小。請參閱咱們關於線程的文檔,瞭解爲何這種架構可以實現Realm的一些出色性能,併發性和安全性優點。

爲了不進行昂貴的系統調用,Realm文件不多在運行時縮小。相反,它們以特定的大小增量增加,新數據被寫入文件內跟蹤的未使用空間內。可是,可能存在Realm文件的重要部分由未使用的空間組成的狀況。爲了解決這個問題,您能夠shouldCompactOnLaunch在Realm的配置對象上設置block屬性,以肯定在第一次打開時是否應該壓縮Realm文件。例如:

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.shouldCompactOnLaunch = ^BOOL(NSUInteger totalBytes, NSUInteger usedBytes) { // totalBytes refers to the size of the file on disk in bytes (data + free space) // usedBytes refers to the number of bytes used by data in the file // Compact if the file is over 100MB in size and less than 50% 'used' NSUInteger oneHundredMB = 100 * 1024 * 1024; return (totalBytes > oneHundredMB) && ((double)usedBytes / totalBytes) < 0.5; }; NSError *error = nil; // Realm is compacted on the first open if the configuration block conditions were met. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error]; if (error) { // handle error compacting or opening Realm }

壓縮操做經過讀取Realm文件的所有內容,將其重寫到不一樣位置的新文件,而後替換原始文件來工做。根據文件中的數據量,這多是一項昂貴的操做。

咱們鼓勵您嘗試使用這些數字來肯定在常常執行壓縮和讓Realm文件變得過大之間取得良好平衡。

最後,若是另外一個進程正在訪問Realm,即便知足配置塊的條件,也會跳過壓縮。這是由於在訪問Realm時沒法安全地執行壓縮。

shouldCompactOnLaunch同步域不支持設置塊。這是由於壓縮不會保留事務日誌,必須保留事務日誌以進行同步。

刪除Realm文件

在某些狀況下,例如清除緩存或重置整個數據集,從磁盤中徹底刪除Realm文件多是合適的。

由於Realm避免將數據複製到內存中,除非絕對須要,因此Realm管理的全部對象都包含對磁盤上文件的引用,而且必須先釋放它才能安全刪除文件。這包括從讀取(或加入)的全部對象的境界,全部RLMArrayRLMResults以及RLMThreadSafeReference目的和RLMRealm自己。

實際上,這意味着刪除Realm文件應該在應用程序啓動以前在打開Realm以前完成,或者在僅在顯式自動釋放池中打開Realm以後完成,這樣能夠確保全部Realm對象都已被釋放。

最後,雖然不是絕對必要,但您應該刪除輔助Realm文件以及主Realm文件以徹底清除全部相關文件。

@autoreleasepool { // all Realm usage here } NSFileManager *manager = [NSFileManager defaultManager]; RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; NSArray<NSURL *> *realmFileURLs = @[ config.fileURL, [config.fileURL URLByAppendingPathExtension:@"lock"], [config.fileURL URLByAppendingPathExtension:@"note"], [config.fileURL URLByAppendingPathExtension:@"management"] ]; for (NSURL *URL in realmFileURLs) { NSError *error = nil; [manager removeItemAtURL:URL error:&error]; if (error) { // handle error } }

楷模

領域數據模型被定義爲具備常規屬性的常規Objective-C類。建立一個,只是子類RLMObject或現有的Realm模型類。領域模型對象的功能大多與其餘任何Objective-C對象同樣。您能夠在它們上定義本身的方法,使它們符合協議,並像使用任何其餘對象同樣使用它們。主要限制是您只能在建立它的線程上使用對象,而且您沒法直接爲任何持久性屬性訪問其ivars。

關係和嵌套數據結構經過包含目標類型的屬性或RLMArray類型的對象列表來建模RLMArray實例也可用於建模原始值的集合(例如,字符串或整數數組)。

#import <Realm/Realm.h> @class Person; // Dog model @interface Dog : RLMObject @property NSString *name; @property Person *owner; @end RLM_ARRAY_TYPE(Dog) // define RLMArray<Dog> // Person model @interface Person : RLMObject @property NSString *name; @property NSDate *birthdate; @property RLMArray<Dog *><Dog> *dogs; @end RLM_ARRAY_TYPE(Person) // define RLMArray<Person> // Implementations @implementation Dog @end // none needed @implementation Person @end // none needed

因爲Realm在啓動時會解析代碼中定義的全部模型,所以它們必須所有有效,即便它們從未使用過。

當使用Swift中的Realm時,該Swift.reflect(_:)函數用於肯定有關模型的信息,這須要調用init()成功。這意味着全部非可選屬性都必須具備默認值。

有關詳細信息,請參閱咱們的API文檔RLMObject

支持的屬性類型

境界支持如下屬性類型:BOOLboolintNSIntegerlonglong longfloatdoubleNSStringNSDateNSData,和NSNumber 標記與特定類型

CGFloat 不鼓勵使用屬性,由於類型不是平臺無關的。

你能夠用RLMArray<Object *><Object>RLMObject子類關係,如一對多和一對一的模型。

RLMArray支持Objective-C泛型。如下是屬性定義的不一樣組件的含義以及它們有用的緣由:

  • RLMArray:屬性類型。
  • <Object *>:通用專業化。這有助於防止在編譯時使用具備錯誤對象類型的數組。
  • <Object>RLMArray符合的協議這使Realm可以知道如何在運行時專門化該模型的模式。

必需的屬性

默認狀況下,NSString *NSData *,和NSDate *屬性容許您設置它們nil若是要要求存在值,請覆蓋子類+requiredProperties方法RLMObject

例如,使用如下模型定義,嘗試將人員的名稱設置爲nil將拋出異常,但nil容許將其生日設置爲:

@interface Person : RLMObject @property NSString *name; @property NSDate *birthday; @end @implementation Person + (NSArray *)requiredProperties { return @[@"name"]; } @end

使用NSNumber *屬性存儲可選數字由於境界使用爲不一樣類型的數字不一樣的存儲格式中,屬性必須具備標記之一RLMIntRLMFloatRLMDouble,或RLMBool分配給屬性的全部值都將轉換爲指定的類型。

請注意,NSDecimalNumber值只能分配給RLMDoubleRealm屬性,而且Realm將存儲值的雙精度浮點近似值,而不是基礎十進制值。

若是咱們想存儲某人的年齡而不是他們的生日,同時仍容許nil他們的年齡未知:

@interface Person : RLMObject @property NSString *name; @property NSNumber<RLMInt> *age; @end @implementation Person + (NSArray *)requiredProperties { return @[@"name"]; } @end

RLMObject子類屬性老是能夠nil,所以不能包含在內requiredProperties而且RLMArray不支持存儲nil

主鍵

覆蓋+primaryKey以設置模型的主鍵。聲明主鍵能夠有效地查找和更新對象,併爲每一個值強制實現惟一性。將具備主鍵的對象添加到Realm後,沒法更改主鍵。

@interface Person : RLMObject @property NSInteger id; @property NSString *name; @end @implementation Person + (NSString *)primaryKey { return @"id"; } @end

索引屬性

要索引屬性,請覆蓋+indexedProperties與主鍵同樣,索引使寫入速度稍慢,但使查詢使用相等性和IN運算符更快。(它還會使您的Realm文件略大,以存儲索引。)最好只在優化特定狀況下的讀取性能時添加索引。

@interface Book : RLMObject @property float price; @property NSString *title; @end @implementation Book + (NSArray *)indexedProperties { return @[@"title"]; } @end

Realm支持對字符串,整數,布爾值和NSDate屬性進行索引

忽略屬性

若是您不想將模型中的字段保存到其Realm,請覆蓋+ignoredProperties領域不會干擾這些屬性的正常運行; 他們將獲得伊娃的支持,你能夠自由地覆蓋他們的二傳手和吸氣者。

@interface Person : RLMObject @property NSInteger tmpID; @property (readonly) NSString *name; // read-only properties are automatically ignored @property NSString *firstName; @property NSString *lastName; @end @implementation Person + (NSArray *)ignoredProperties { return @[@"tmpID"]; } - (NSString *)name { return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName]; } @end

忽略的屬性與普通屬性徹底相同。它們不支持任何特定於Realm的功能(例如,它們不能在查詢中使用,也不會觸發通知)。仍然可使用KVO觀察它們。

默認屬性值

+defaultPropertyValues每次建立對象時覆蓋以提供默認值。

@interface Book : RLMObject @property float price; @property NSString *title; @end @implementation Book + (NSDictionary *)defaultPropertyValues { return @{@"price" : @0, @"title": @""}; } @end

屬性屬性

境界忽略Objective-C的屬性的屬性等nonatomicatomicstrongcopyweak,等等,這些都是沒有意義爲境界存儲; 它有本身的優化存儲語義。所以,爲了不誤讀任何人閱讀您的代碼,咱們建議您編寫模型而不要歸因於任何屬性。可是,若是設置屬性的屬性,它們將被用於直到RLMObject被添加到一個境界。

不管是否RLMObject由Realm管理,getter和setter的自定義名稱都能正常工做

由於非託管Realm對象(不是由Realm管理的Realm模型類的實例)只是普通的NSObjects,因此屬性屬性就像任何其餘屬性同樣被觀察到NSObject

若是將Realm Objective-C與Swift一塊兒使用,則模型屬性須要該@objc dynamic var屬性才能使這些屬性成爲底層數據庫數據的訪問者。(您也可使用聲明類@objcMembers和聲明模型屬性dynamic var。)

財產備忘單

此表提供了聲明模型屬性的便捷參考。

類型 非可選 可選的
布爾 @property BOOL value; @property NSNumber<RLMBool> *value;
詮釋 @property int value; @property NSNumber<RLMInt> *value;
浮動 @property float value; @property NSNumber<RLMFloat> *value;
@property double value; @property NSNumber<RLMDouble> *value;
@property NSString *value; 1 @property NSString *value;
數據 @property NSData *value; 1 @property NSData *value;
日期 @property NSDate *value; 1 @property NSDate *value;
賓語 不適用:必須是可選的 @property Object *value;
名單 @property RLMArray<Class *><Class> *value; 不適用:必須是非選擇性的
LinkingObjects @property (readonly) RLMLinkingObjects<Object *> *value; 2 不適用:必須是非選擇性的

必須聲明Objective-C引用類型的必需屬性以及+requiredProperties方法的重寫實現

@implementation MyModel + (NSArray *)requiredProperties { // The array must contain the names of all required properties. return @[@"value"]; } @end

使用Realm對象

自動更新對象

RLMObject實例是實時的,自動更新基礎數據的視圖; 你永遠沒必要刷新對象。修改對象的屬性將當即反映在引用同一對象的任何其餘實例中。

Dog *myDog = [[Dog alloc] init]; myDog.name = @"Fido"; myDog.age = 1; [realm transactionWithBlock:^{ [realm addObject:myDog]; }]; Dog *myPuppy = [[Dog objectsWhere:@"age == 1"] firstObject]; [realm transactionWithBlock:^{ myPuppy.age = 2; }]; myDog.age; // => 2

這不只能夠保持Realm的快速和高效,還可使您的代碼更簡單,更具反應性。若是您的UI代碼依賴於特定的Realm對象,則在觸發UI重繪以前,您無需擔憂刷新或從新獲取它。

您能夠訂閱Realm通知,以瞭解對象中的Realm數據什麼時候更新,指示什麼時候應刷新應用程序的UI。

模型繼承

Realm容許模型進一步子類化,容許跨模型重用代碼,可是一些致使運行時富類多態的Cocoa特性不可用。這是可能的:

  • 父類的類方法,實例方法和屬性在其子類中繼承。
  • 將父類做爲參數的方法和函數能夠在子類上運行。

目前沒法實現如下目標:

  • 多態類之間的轉換(即,子類到子類,子類到父類,父類到子類等)
  • 同時查詢多個類
  • 多級容器(RLMArrayRLMResults

將此功能添加到Realm是路線圖目前,咱們提供了一些代碼示例,用於解決一些更常見的模式。

或者,若是您的實現容許,咱們建議使用如下類組合模式來構建包含來自其餘類的邏輯的子類:

// Base Model @interface Animal : RLMObject @property NSInteger age; @end @implementation Animal @end // Models composed with Animal @interface Duck : RLMObject @property Animal *animal; @property NSString *name; @end @implementation Duck @end @interface Frog : RLMObject @property Animal *animal; @property NSDate *dateProp; @end @implementation Frog @end // Usage Duck *duck = [[Duck alloc] initWithValue:@{@"animal" : @{@"age" : @(3)}, @"name" : @"Gustav" }];

集合

Realm有幾種類型能夠幫助表示對象組,咱們稱之爲「Realm集合」:

  1. RLMResults,一個表示從查詢中檢索的對象的類
  2. RLMArray,一個表示模型中多對多關係的類
  3. RLMLinkingObjects,一個表示模型中反比關係的類
  4. RLMCollection,一個定義全部Realm集合符合的公共接口的協議。

Realm集合類型各自符合RLMCollection協議,這確保它們的行爲一致。該協議的繼承NSFastEnumeration使得它能夠與其餘Foundation集合同樣使用。在此協議中聲明瞭其餘常見的Realm集合API,例如查詢,排序和聚合操做等。RLMArrays具備超出協議接口的額外變異操做,例如添加和刪除對象或值。

使用該RLMCollection協議,您能夠編寫能夠在任何Realm集合上運行的通用代碼:

@implementation MyObject - (void)operateOnCollection:(id<RLMCollection>)collection { // Collection could be either RLMResults or RLMArray NSLog(@"operating on collection of %@s", collection.objectClassName); } @end

在領域之間複製對象

將Realm對象複製到其餘Realms就像傳入原始對象同樣簡單+[RLMObject createInRealm:withValue:]例如,[MyRLMObjectSubclass createInRealm:otherRealm withValue:originalObjectInstance]請記住,Realm對象只能從首次建立它們的線程中訪問,所以該副本僅適用於同一線程上的Realms。

請注意,+[RLMObject createInRealm:withValue:]不支持處理循環對象圖。不要直接或間接傳入包含涉及引用其父項的對象的關係的對象。

關係

您能夠將任意兩個Realm對象連接在一塊兒。Realm中的關係很便宜:遍歷連接在速度或內存方面並不昂貴。讓咱們探索不一樣類型的關係,Realm容許您在對象之間進行定義。

RLMObject經過使用RLMObjectRLMArray屬性連接a RLMArrays具備很是相似的接口NSArray,而且RLMArray可使用索引下標來訪問a中包含的對象NSArray不一樣RLMArrays是鍵入的,只保存RLMObject單個子類型。有關更多詳細信息,請參閱API文檔RLMArray

假設您的Person模型已經定義(參見模型),讓咱們建立一個名爲的模型Dog

// Dog.h @interface Dog : RLMObject @property NSString *name; @end

許多到一

要設置多對一或一對一關係,請爲模型提供其類型爲您的RLMObject子類之一的屬性

// Dog.h @interface Dog : RLMObject // ... other property declarations @property Person *owner; @end

您能夠像使用任何其餘屬性同樣使用此屬性:

Person *jim = [[Person alloc] init]; Dog *rex = [[Dog alloc] init]; rex.owner = jim;

使用RLMObject屬性時,可使用常規屬性語法訪問嵌套屬性。例如,rex.owner.address.country將遍歷對象圖並根據須要自動從Realm中獲取每一個對象。

許多一對多

您可使用RLMArray屬性建立與任意數量的對象或支持的原始值的關係RLMArrays包含RLMObject單個類型的其餘s或原始值,而且具備很是相似的接口NSMutableArray

RLMArray包含Realm對象的s能夠存儲對同一Realm對象的多個引用,包括具備主鍵的對象。例如,您能夠建立一個空的RLMArray並將相同的對象插入其中三次; RLMArray而後將返回若是元素該對象在任何索引0,1和2被訪問。

RLMArrays能夠存儲原始值來代替Realm對象。爲了作到這一點,一個約束RLMArray與下列協議之一:RLMBoolRLMIntRLMFloatRLMDoubleRLMStringRLMData,或RLMDate

默認狀況下,RLMArray包含基本類型的s可能包含空值(由表示NSNull)。指示數組是必需的(經過覆蓋+requiredProperties:數組所屬的模型對象類型)也將使數組中的值成爲必需。

讓咱們dogs在咱們的Person模型上添加一個連接到多隻狗屬性首先,咱們RLMArray<Dog>使用Dog模型接口底部的宏來定義一個類型

// Dog.h @interface Dog : RLMObject // ... property declarations @end RLM_ARRAY_TYPE(Dog) // Defines an RLMArray<Dog> type

RLM_ARRAY_TYPE宏建立一個協議,以使得可以使用的RLMArray<Dog>語法。若是宏未放置在模型接口的底部,則可能必須轉發聲明模型類。

而後,您能夠聲明該RLMArray<Dog>類型的屬性

// Person.h @interface Person : RLMObject // ... other property declarations @property RLMArray<Dog *><Dog> *dogs; @end

您能夠RLMArray照常訪問和分配屬性:

// Jim is owner of Rex and all dogs named "Fido" RLMResults<Dog *> *someDogs = [Dog objectsWhere:@"name contains 'Fido'"]; [jim.dogs addObjects:someDogs]; [jim.dogs addObject:rex];

請注意,雖然能夠分配nilRLMArray屬性,但這隻會「清空」數組而不是刪除數組。這意味着您始終能夠將對象添加到RLMArray屬性,即便在將其設置爲以後也是如此nil

RLMArray 保證屬性保持其插入順序。

請注意,RLMArray當前不支持查詢包含原始值的s。

反向關係

關係是單向的。就拿咱們的兩個類Person,並Dog做爲一個例子。若是Person.dogs連接到Dog實例,則能夠按照連接從Persona到a Dog,可是沒法從a Dog到其Person對象。您能夠設置Dog.owner連接到的一對一屬性Person,但這些連接彼此獨立。添加一個Dogto Person.dogs不會將該狗的Dog.owner屬性設置爲正確Person爲解決此問題,Realm提供連接對象屬性以表示反向關係。

@interface Dog : RLMObject @property NSString *name; @property NSInteger age; @property (readonly) RLMLinkingObjects *owners; @end @implementation Dog + (NSDictionary *)linkingObjectsProperties { return @{ @"owners": [RLMPropertyDescriptor descriptorWithClass:Person.class propertyName:@"dogs"], }; } @end

經過連接對象屬性,您能夠從特定屬性獲取連接到給定對象的全部對象。一個Dog對象能夠有一個名爲屬性owners包含全部的Person有這個確切的對象Dog在他們的對象dogs屬性。建立owners類型屬性,RLMLinkingObjects而後覆蓋+[RLMObject linkingObjectsProperties]以指示ownersPerson模型對象的關係

對象的全部更改(添加,修改和刪除)必須在寫入事務中完成。

Realm對象能夠被實例化並用做非託管對象(即還沒有添加到Realm),就像常規的Objective-C對象同樣。可是,要在線程之間共享對象或在應用程序啓動之間從新使用它們,必須將它們添加到Realm。向Realm添加對象必須在寫入事務中完成。因爲寫入事務會產生不可忽略的開銷,所以您應該構建代碼以最大限度地減小寫入事務的數量。

領域寫操做是同步和阻塞,而不是異步。若是線程A開始寫操做,則線程B在線程A完成以前在同一個域上開始寫操做,線程A必須在線程B的寫操做發生以前完成並提交其事務。寫操做始終自動刷新beginWrite(),所以重疊寫入不會建立競爭條件。

由於寫事務可能會失敗,就像任何其餘的磁盤IO操做,都-[RLMRealm transactionWithBlock:]-[RLMRealm commitWriteTransaction]可選須要一個NSError指針參數,因此你能夠處理,並從失敗就像跑出來的磁盤空間進行恢復。沒有其餘可恢復的錯誤。爲簡潔起見,咱們的代碼示例不處理這些錯誤,但您確定應該在生產應用程序中。

建立對象

定義模型後,能夠實例化子RLMObject類並將新實例添加到Realm。考慮這個簡單的模型:

// Dog model @interface Dog : RLMObject @property NSString *name; @property NSInteger age; @end // Implementation @implementation Dog @end

咱們能夠用幾種方式建立新對象:

// (1) Create a Dog object and then set its properties Dog *myDog = [[Dog alloc] init]; myDog.name = @"Rex"; myDog.age = 10; // (2) Create a Dog object from a dictionary Dog *myOtherDog = [[Dog alloc] initWithValue:@{@"name" : @"Pluto", @"age" : @3}]; // (3) Create a Dog object from an array Dog *myThirdDog = [[Dog alloc] initWithValue:@[@"Pluto", @3]];
  1. 最明顯的是使用指定的初始化程序來建立對象。請注意,必須先設置全部必需的屬性,而後才能將對象添加到Realm。
  2. 也可使用適當的鍵和值從字典建立對象。
  3. 最後,RLMObject可使用數組實例化子類。數組中的值必須與模型中的相應屬性的順序相同。

數組中的值應與存儲在Realm中的屬性相對應 - 您不該指定忽略的屬性或計算屬性的值。

建立對象後,能夠將其添加到Realm:

// Get the default Realm RLMRealm *realm = [RLMRealm defaultRealm]; // You only need to do this once (per thread) // Add to Realm with transaction [realm beginWriteTransaction]; [realm addObject:myDog]; [realm commitWriteTransaction];

將對象添加到Realm後,您能夠繼續使用它,而且您對其所作的全部更改都將被保留(而且必須在寫入事務中進行)。在提交寫入事務時,對使用相同Realm的其餘線程能夠進行任何更改。

請注意,寫入會相互阻塞,而且若是正在進行屢次寫入,則會阻止它們建立的線程。這相似於其餘持久性解決方案,咱們建議您在這種狀況下使用一般的最佳實踐:將寫入卸載到單獨的線程。

因爲Realm的MVCC架構,在寫事務打開時不會阻止讀取。除非您須要同時從多個線程同時進行寫入,不然您應該支持更大的寫入事務,這些事務對許多細粒度的寫入事務執行更多操做。當您向Realm提交寫入事務時,將通知該Realm的全部其餘實例,並自動更新

有關更多詳細信息,請參閱RLMRealmRLMObject

嵌套對象

若是對象具備RLMObjects或RLMArray屬性,則可使用嵌套數組和/或字典遞歸設置這些屬性您只需使用表示其屬性的字典或數組替換每一個對象:

// Instead of using already existing dogs... Person *person1 = [[Person alloc] initWithValue:@[@"Jane", @30, @[aDog, anotherDog]]]; // ...we can create them inline Person *person2 = [[Person alloc] initWithValue:@[@"Jane", @30, @[@[@"Buster", @5], @[@"Buddy", @6]]]];

這適用於嵌套數組和字典的任意組合。請注意,a RLMArray可能只包含RLMObjects,而不是基本類型NSString

更新對象

Realm提供了一些更新對象的方法,全部這些方法都根據具體狀況提供不一樣的權衡。

鍵入的更新

您能夠經過在寫入事務中設置其屬性來更新任何對象。

// Update an object with a transaction [realm beginWriteTransaction]; author.name = @"Thomas Pynchon"; [realm commitWriteTransaction];

鍵值編碼

RLMObjectRLMResultRLMArray全部符合鍵值編碼(KVC)。當您須要肯定在運行時更新哪一個屬性時,這很是有用。

將KVC應用於集合是批量更新對象的好方法,而不會在爲每一個項建立訪問器時迭代集合。

RLMResults<Person *> *persons = [Person allObjects]; [[RLMRealm defaultRealm] transactionWithBlock:^{ [[persons firstObject] setValue:@YES forKeyPath:@"isFirst"]; // set each person's planet property to "Earth" [persons setValue:@"Earth" forKeyPath:@"planet"]; }];

具備主鍵的對象

若是模型類包含主鍵,則可使用Realm智能更新或基於主鍵值添加對象-[RLMRealm addOrUpdateObject:]

// Creating a book with the same primary key as a previously saved book Book *cheeseBook = [[Book alloc] init]; cheeseBook.title = @"Cheese recipes"; cheeseBook.price = 9000; cheeseBook.id = 1; // Updating book with id = 1 [realm beginWriteTransaction]; [realm addOrUpdateObject:cheeseBook]; [realm commitWriteTransaction];

若是Book數據庫中已存在主鍵值爲「1」的對象,則只會更新該對象。若是它不存在,則將Book建立一個全新的對象並將其添加到數據庫中。

您還能夠經過僅傳遞要更新的值的子集以及主鍵來部分更新具備主鍵的對象:

// Assuming a "Book" with a primary key of `1` already exists. [realm beginWriteTransaction]; [Book createOrUpdateModifiedInRealm:realm withValue:@{@"id": @1, @"price": @9000.0}]; // the book's `title` property will remain unchanged. [realm commitWriteTransaction];

您不能將本章中顯示的那些方法(以此結尾OrUpdate)與未定義主鍵的對象一塊兒調用

當更新你能夠選擇任何對象經過調用都設置爲傳入的值,或實際上已更改成新的值只有屬性的現有對象的屬性createOrUpdateInReam:createOrUpdateModifiedInRealm:這個決定有一些影響:

  1. 產生了什麼通知。使用對象通知時createOrUpdateInRealm:將報告value傳遞的對象中存在的全部屬性都已修改,同時createOrUpdateModifiedInRealm:將僅致使報告具備新值的屬性。
  2. 使用Realm Object Server時如何合併衝突的寫入。假設您有一本書的標題爲奶酪食譜,價格爲9000,而且一個客戶與另外一個客戶[Book createOrUpdateInRealm:realm value:@{@"id": @1, @"title": @"Fruit recipes", @"price": @9000}]同時打電話[Book createOrUpdateInRealm:realm value:@{@"id": @1, @"title": @"Cheese recipes", @"price": @4000}]由於全部屬性都已設置,因此合併後的結果將是一本書的標題爲奶酪食譜,價格爲4000或一本書的標題爲水果食譜,價格爲9000.若是相反,他們調用createOrUpdateModifiedInRealm:的結果將是一本書標題爲水果食譜,價格爲4000。
  3. 性能。檢查屬性是否已更改有少許開銷createOrUpdateModifiedInRealm:可是,若是屬性未更改,createOrUpdateInRealm:則會寫入更多數據,這二者都會增長必須寫入本地Realm的數據量,並增長Realm對象服務器須要處理的指令數。

若是有疑問,createOrUpdateModifiedInRealm:可能就是你想要的那個。

請注意,更新對象時,NSNull仍被視爲可選屬性的有效值若是您提供具備NSNull屬性值的字典,則這些字典將應用於您的對象,而且這些屬性將被清空。爲確保您不會遇到任何計劃外數據丟失,請確保在使用此方法時僅提供您要更新的屬性。

刪除對象

將要刪除的對象傳遞-[RLMRealm deleteObject:]給寫入事務中方法。

// cheeseBook stored in Realm // Delete an object with a transaction [realm beginWriteTransaction]; [realm deleteObject:cheeseBook]; [realm commitWriteTransaction];

您還能夠刪除存儲在Realm中的全部對象。請注意,Realm文件將在磁盤上保持其大小,以便有效地將該空間重用於未來的對象。

// Delete all objects from the realm [realm beginWriteTransaction]; [realm deleteAllObjects]; [realm commitWriteTransaction];

查詢

查詢返回一個RLMResults實例,其中包含RLMObject的集合RLMResults有一個很是類似的接口,NSArray而且RLMResults可使用索引下標訪問a中包含的對象NSArray不一樣RLMResults是鍵入的,只能保存RLMObject單個子類的類型。

全部查詢(包括查詢和屬性訪問)在Realm中都是惰性的。只有在訪問屬性時纔會讀取數據。

查詢的結果不是數據的副本:修改查詢結果(在寫入事務中)將直接修改磁盤上的數據。一樣,您能夠直接從a中包含遍歷關係RLMObjectRLMResults

延遲執行查詢直到使用結果。這意味着將幾個臨時連接RLMResults以對數據進行排序和過濾不會執行處理中間狀態的額外工做。

一旦執行了查詢,或者添加通知塊RLMResults就會更新Realm中的更改,並在可能的狀況下在後臺線程上執行查詢。

用於從一個域是檢索對象的最基本的方法+[RLMObject allObjects],它返回一個RLMResults全部的RLMObject子類類型的實例從默認境界被查詢。

RLMResults<Dog *> *dogs = [Dog allObjects]; // retrieves all Dogs from the default Realm

過濾

若是您熟悉NSPredicate,那麼您已經知道如何在Realm中查詢。RLMObjectsRLMRealmRLMArray,和RLMResults全部提供容許您查詢具體方法爲RLMObject經過簡單地傳遞一個實例NSPredicate的實例,謂語字符串,或者就像你的查詢謂詞時格式字符串NSArray

例如,如下內容將經過調用[RLMObject objectsWhere:]從默認Realm中檢索名稱以「B」開頭的全部棕褐色狗來擴展咱們以前的示例

// Query using a predicate string RLMResults<Dog *> *tanDogs = [Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"]; // Query using an NSPredicate NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@", @"tan", @"B"]; tanDogs = [Dog objectsWithPredicate:pred];

有關構建謂詞和使用咱們的NSPredicate Cheatsheet的更多信息,請參閱Apple的Predicates編程指南Realm支持許多常見謂詞:

  • 比較操做數能夠是屬性名稱或常量。至少有一個操做數必須是屬性名稱。
  • 比較操做符==<= <> =>!=,和BETWEEN都支持intlonglong longfloatdouble,和NSDate屬性類型,例如age == 45
  • 身份比較==!=,例如[Employee objectsWhere:@"company == %@", company]
  • 布爾屬性支持比較運算符==!=
  • 對於NSStringNSData屬性,支持==!=BEGINSWITHCONTAINSENDSWITH運算符,例如name CONTAINS 'Ja'
  • 對於NSString屬性,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'}
  • 無比較==!=,例如[Company objectsWhere:@"ceo == nil"]請注意,Realm將其nil視爲特殊值而不是缺乏值; 與SQL不一樣,nil等於本身。
  • 任何比較,例如ANY student.age < 21
  • 支持屬性的聚合表達式@ count@ min@ max@ sum@avg,例如,查找全部員工人數超過五人的公司。RLMArrayRLMResults[Company objectsWhere:@"employees.@count > 5"]
  • 子查詢受如下限制支持:
    • @count是惟一能夠應用於SUBQUERY表達式的運算符
    • SUBQUERY(…).@count表達式必須以恆定的相比較。
    • 尚不支持相關的子查詢。

[RLMObject objectsWhere:]

排序

RLMResults容許您根據鍵路徑,屬性或一個或多個排序描述符指定排序條件和順序。例如,如下調用按名稱按字母順序對上面示例中返回的狗進行排序:

// Sort tan dogs with names starting with "B" by name RLMResults<Dog *> *sortedDogs = [[Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"] sortedResultsUsingKeyPath:@"name" ascending:YES];

關鍵路徑也多是一對一關係的屬性

RLMResults<Person *> *dogOwners = [Person allObjects]; RLMResults<Person *> *ownersByDogAge = [dogOwners sortedResultsUsingKeyPath:@"dog.age" ascending:YES];

請注意,sortedResultsUsingKeyPath:而且sortedResultsUsingProperty:不支持多個屬性做爲排序條件,而且不能連接(僅使用最後一次調用sortedResults...)。要按多個屬性排序,請使用sortedResultsUsingDescriptors:多個RLMSortDescriptor對象。

有關更多信息,請參閱

請注意,Results僅在查詢排序時保證順序保持一致。出於性能緣由,不保證保留插入順序。若是您須要維護插入順序,這裏提出一些解決方案

連接查詢

與須要爲每一個連續查詢單獨訪問數據庫服務器的傳統數據庫相比,Realm查詢引擎的一個獨特屬性是可以以很是小的事務開銷連接查詢。

若是你想要一個棕褐色狗的結果集,以及名字也以'B'開頭的棕褐色狗,你能夠連接兩個這樣的查詢:

RLMResults<Dog *> *tanDogs = [Dog objectsWhere:@"color = 'tan'"]; RLMResults<Dog *> *tanDogsWithBNames = [tanDogs objectsWhere:@"name BEGINSWITH 'B'"];

自動更新結果

RLMResults實例是實時的,自動更新基礎數據的視圖,這意味着永遠沒必要從新獲取結果。它們老是在當前線程上反映Realm的當前狀態,包括在當前線程的寫入事務期間。對此的一個例外是使用for...in枚舉時,枚舉開始時將始終枚舉與查詢匹配的對象,即便其中一些被刪除或修改成在枚舉期間被過濾器排除。

RLMResults<Dog *> *puppies = [Dog objectsInRealm:realm where:@"age < 2"]; puppies.count; // => 0 [realm transactionWithBlock:^{ [Dog createInRealm:realm withValue:@{@"name": @"Fido", @"age": @1}]; }]; puppies.count; // => 1

這適用於全部RLMResults:全部對象,已過濾和連接。

這種屬性RLMResults不只使Realm快速高效,並且使您的代碼更簡單,更具反應性。例如,若是視圖控制器依賴於查詢結果,則能夠將其存儲RLMResults在屬性中並對其進行訪問,而無需確保在每次訪問以前刷新其數據。

您能夠訂閱Realm通知,以瞭解Realm數據什麼時候更新,指示應該刷新應用程序的UI的時間,而無需從新獲取RLMResults

因爲結果是自動更新的,所以不要依賴索引和計數保持不變是很重要的。RLMResults凍結的惟一時間是對其進行快速枚舉,這樣就能夠在枚舉對象時改變匹配查詢的對象:

[realm beginWriteTransaction]; for (Person *person in [Person objectsInRealm:realm where:@"age == 10"]) { person.age++; } [realm commitWriteTransaction];

或者,使用鍵值編碼來執行操做RLMResults

限制結果

大多數其餘數據庫技術提供了從查詢中「分頁」結果的能力(例如SQLite中的'LIMIT'關鍵字)。這一般是爲了不從磁盤中讀取太多內容,或者一次將太多結果拉入內存中。

因爲Realm中的查詢是惰性的,所以根本不須要執行這種分頁行爲,由於Realm只會在顯式訪問後從查詢結果中加載對象。

若是出於UI相關或其餘實現緣由,您須要查詢中特定的對象子集,那麼就像獲取RLMResults對象同樣簡單,只讀取您須要的對象。

// Loop through the first 5 Dog objects // restricting the number of objects read from disk RLMResults<Dog *> *dogs = [Dog allObjects]; for (NSInteger i = 0; i < 5; i++) { Dog *dog = dogs[i]; // ... }

遷移

使用任何數據庫時,您的數據模型可能會隨着時間的推移而發生變化。因爲Realm中的數據模型被定義爲標準的Objective-C類,所以進行模型更改就像更改任何其餘Objective-C類同樣簡單。

假設咱們有如下Person模型:

@interface Person : RLMObject @property NSString *firstName; @property NSString *lastName; @property int age; @end

咱們但願更新數據模型以要求fullName屬性,而不是分隔名和姓。爲此,咱們只需將對象界面更改成如下內容:

@interface Person : RLMObject @property NSString *fullName; @property int age; @end

此時,若是您使用之前的型號版本保存了任何數據,則Realm在代碼中定義的內容與Realm在磁盤上看到的數據之間將存在不匹配。發生這種狀況時,除非您運行遷移,不然在嘗試打開現有文件時將引起異常。

請注意,在遷移期間默認屬性值不會應用於現有對象上的新對象或新屬性。咱們認爲這是一個錯誤,並將其跟蹤爲#1793

本地遷移

本地遷移由設置RLMRealmConfiguration.schemaVersion定義RLMRealmConfiguration.migrationBlock您的遷移塊提供了將數據模型從先前模式轉換爲新模式的全部邏輯。RLMRealm使用此配置建立a 時,若是須要遷移,將應用遷移塊以更新RLMRealm給定的架構版本。

假設咱們想要遷移Person先前聲明模型。最小必要的遷移塊將以下:

// Inside your [AppDelegate didFinishLaunchingWithOptions:] RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; // Set the new schema version. This must be greater than the previously used // version (if you've never set a schema version before, the version is 0). config.schemaVersion = 1; // Set the block which will be called automatically when opening a Realm with a // schema version lower than the one set above config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) { // We haven’t migrated anything yet, so oldSchemaVersion == 0 if (oldSchemaVersion < 1) { // Nothing to do! // Realm will automatically detect new properties and removed properties // And will update the schema on disk automatically } }; // Tell Realm to use this new configuration object for the default Realm [RLMRealmConfiguration setDefaultConfiguration:config]; // Now that we've told Realm how to handle the schema change, opening the file // will automatically perform the migration [RLMRealm defaultRealm];

咱們至少須要使用空塊更新版本,以指示架構已由Realm升級(自動)。

更新值

雖然這是可接受的最小遷移,但咱們可能但願使用此塊來填充任何fullName有意義的新屬性(在本例中)。在遷移塊中,咱們能夠調用[RLMMigration enumerateObjects:block:]枚舉RLMObject某種類型的一種,並應用任何須要的遷移邏輯。請注意每一個枚舉如何RLMObject經過oldObject變量訪問現有實例,並經過如下方式訪問更新的實例newObject

// Inside your [AppDelegate didFinishLaunchingWithOptions:] RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.schemaVersion = 1; config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) { // We haven’t migrated anything yet, so oldSchemaVersion == 0 if (oldSchemaVersion < 1) { // The enumerateObjects:block: method iterates // over every 'Person' object stored in the Realm file [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) { // combine name fields into a single field newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@", oldObject[@"firstName"], oldObject[@"lastName"]]; }]; } }; [RLMRealmConfiguration setDefaultConfiguration:config];

遷移成功完成後,您的應用程序能夠像往常同樣訪問Realm及其全部對象。

重命名屬性

做爲遷移的一部分在類上重命名屬性比複製值和保留關係而不是複製它們更有效。

要在遷移期間重命名屬性,請確保新模型具備具備新名稱的屬性,而且沒有具備舊名稱的屬性。

若是新屬性具備不一樣的可爲空性或索引設置,則將在重命名操做期間應用這些設置。

這裏是你如何能夠重命名PersonyearsSinceBirth屬性age

// Inside your [AppDelegate didFinishLaunchingWithOptions:] RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.schemaVersion = 1; config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) { // We haven’t migrated anything yet, so oldSchemaVersion == 0 if (oldSchemaVersion < 1) { // The renaming operation should be done outside of calls to `enumerateObjects:`. [migration renamePropertyForClass:Person.className oldName:@"yearsSinceBirth" newName:@"age"]; } }; [RLMRealmConfiguration setDefaultConfiguration:config];

線性遷移

假設咱們的應用程序有兩個用戶: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拋出異常。若是您的應用程序的架構設置可能會出現這種狀況,您可使用它-[RLMRealm isInWriteTransaction]來肯定您是否已經在寫入事務中。

因爲使用運行循環傳遞通知,所以運行循環上的其餘活動可能會延遲通知的傳遞。當沒法當即傳遞通知時,多個寫入事務的更改可能會合併爲單個通知。

領域通知

通知處理程序能夠在整個Realm上註冊。每次提交涉及該Realm的寫入事務時,不管寫入事務發生在哪一個線程或進程上,都將觸發通知處理程序:

// Observe Realm Notifications token = [realm addNotificationBlock:^(NSString *notification, RLMRealm * realm) { [myViewController updateUI]; }]; // later [token invalidate];

收集通知

收集通知不會收到整個Realm,而是收到細粒度的更改說明。它們包括自上次通知以來已添加,刪除或修改的對象索引。收集通知是異步傳遞的,首先是初始結果,而後是每次寫入事務後再次發送,這會改變集合中的任何對象(或添加新對象)。

能夠經過RLMCollectionChange傳遞給通知塊參數訪問這些更改這個對象保存有關受索引信息deletionsinsertionsmodifications

前兩個,刪除插入,在對象開始和中止成爲集合的一部分時記錄索引。這會將對象添加到Realm或從Realm中刪除它們時考慮在內。爲此,RLMResults當您篩選特定值並更改對象以使其如今與查詢匹配或再也不匹配時也適用。對於基於RLMArrayRLMLinkingObjects包括派生的集合RLMResults當在關係中添加或刪除對象時,這也適用。

只要集合中對象的屬性發生更改,您就會收到有關修改的通知這也發生更改的一對一一對多的關係,雖然通知不會採起反向關係考慮在內。

@interface Dog : RLMObject @property NSString *name; @property NSData *picture; @property NSInteger age; @end @implementation Dog @end RLM_ARRAY_TYPE(Dog) @interface Person : RLMObject @property NSString *name; @property RLMArray<Dog *><Dog> *dogs; @end @implementation Person @end

咱們假設您正在觀察上面的模型代碼給出的狗主人名單。在下列狀況下,您將收到有關匹配Person對象的修改的通知

  • 你修改Personname屬性。
  • 您添加或刪除DogPersondogs財產。
  • 您修改屬於age屬性的屬性DogPerson

這使得能夠離散地控制對UI內容進行的動畫和視覺更新,而不是每次發生通知時任意從新加載全部內容。

- (void)viewDidLoad { [super viewDidLoad]; // Observe RLMResults Notifications __weak typeof(self) weakSelf = self; self.notificationToken = [[Person objectsWhere:@"age > 5"] addNotificationBlock:^(RLMResults<Person *> *results, RLMCollectionChange *changes, NSError *error) { if (error) { NSLog(@"Failed to open Realm on background worker: %@", error); return; } UITableView *tableView = weakSelf.tableView; // Initial run of the query will pass nil for the change information if (!changes) { [tableView reloadData]; return; } // Query results have changed, so apply them to the UITableView [tableView beginUpdates]; [tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView insertRowsAtIndexPaths:[changes insertionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView endUpdates]; }]; } - (void)dealloc { [self.notificationToken invalidate]; }

對象通知

Realm支持對象級通知。您能夠在特定Realm對象上註冊通知,以便在刪除對象時或在對象上的任何託管屬性修改其值時收到通知。(這也適用於將其值設置爲其現有值的託管屬性。)

只有Realm管理的對象可能在其上註冊了通知處理程序。

對於在不一樣線程或不一樣進程中執行的寫入事務,當管理對象的Realm(自動)刷新到包含更改的版本時,將調用該塊,而對於本地寫入事務,它將在某個時刻被調用。寫入事務提交後的將來。

通知處理程序有三個參數。第一個參數deletedBOOL指示對象是否已刪除。若是是這樣YES,其餘參數將爲nil,而且永遠不會再次調用該塊。

第二個參數,changes是一個NSArrayRLMPropertyChange對象。這些對象中的每個都包含已更改的屬性的名稱(做爲字符串),前一個值和當前值。

第三個論點是NSError若是發生涉及對象的錯誤,NSError則將包含有關發生的事件的信息,changes將爲nil,deleted將是NO,而且將永遠再也不調用該塊。

@interface RLMStepCounter : RLMObject @property NSInteger steps; @end @implementation RLMStepCounter @end RLMStepCounter *counter = [[RLMStepCounter alloc] init]; counter.steps = 0; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:counter]; [realm commitWriteTransaction]; __block RLMNotificationToken *token = [counter addNotificationBlock:^(BOOL deleted, NSArray<RLMPropertyChange *> *changes, NSError *error) { if (deleted) { NSLog(@"The object was deleted."); } else if (error) { NSLog(@"An error occurred: %@", error); } else { for (RLMPropertyChange *property in changes) { if ([property.name isEqualToString:@"steps"] && [property.value integerValue] > 1000) { NSLog(@"Congratulations, you've exceeded 1000 steps."); [token invalidate]; token = nil; } } } }];

接口驅動的寫入

Realm中的通知始終是異步傳遞的,所以它們永遠不會阻止主UI線程,從而致使應用程序斷斷續續。可是,有些狀況須要在主線程上同步完成更改,並當即反映在UI中。咱們將這些事務稱爲接口驅動的寫入。

例如,假設用戶將項添加到表視圖中。理想狀況下,UI應該爲此操做設置動畫,並在用戶啓動操做後當即啓動此過程。

可是,當此插入的Realm更改通知稍後傳遞時,它將指示對象已添加到支持表視圖的集合中,咱們將再次嘗試在UI中插入新行。這種雙重插入會致使UI和支持數據之間的狀態不一致,從而致使應用程序崩潰!

執行接口驅動的寫入時,傳遞通知塊的通知令牌,這些通知塊不該對第二次更改作出反應-[RLMRealm commitWriteTransactionWithoutNotifying:error:]

當使用帶有同步Realm的細粒度收集通知時,此功能特別有用,由於之前考慮接口驅動寫入的許多解決方法依賴於控制應用程序什麼時候能夠執行更改的完整狀態。使用同步領域,只要它們被同步就會應用更改,這可能發生在應用程序生命週期的任什麼時候候。

// Observe RLMResults Notifications __weak typeof(self) weakSelf = self; self.notificationToken = [self.collection addNotificationBlock:^(RLMResults<Item *> *results, RLMCollectionChange *changes, NSError *error) { if (error) { NSLog(@"Failed to open Realm on background worker: %@", error); return; } UITableView *tableView = weakSelf.tableView; // Initial run of the query will pass nil for the change information if (!changes) { [tableView reloadData]; return; } // Query results have changed, so apply them to the UITableView [tableView beginUpdates]; [tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView insertRowsAtIndexPaths:[changes insertionsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView endUpdates]; }]; - (void)insertItem { // Perform an interface-driven write on the main thread: [self.collection.realm beginWriteTransaction]; [self.collection insertObject:[Item new] atIndex:0]; // And mirror it instantly in the UI [tableView insertRowsAtIndexPaths:[NSIndexPath indexPathForRow:0 inSection:0] withRowAnimation:UITableViewRowAnimationAutomatic]; // Making sure the change notification doesn't apply the change a second time [self.collection.realm commitWriteTransactionWithoutNotifying:@[token]]; }

關鍵價值觀察

領域對象是符合大多數屬性的鍵值觀察幾乎全部RLMObject子類上的託管(非忽略)屬性都符合KVO,以及invalidated屬性on RLMObjectRLMArrayRLMLinkingObjects使用KVO沒法觀察到屬性。)

觀察RLMObject子類的非託管實例的屬性就像使用任何其餘NSObject子類同樣,但請注意,[realm addObject:obj]在具備任何已註冊的觀察者時,您沒法將對象添加到Realm(使用其餘相似方法)。

觀察託管對象(之前添加到Realm中的對象)的屬性的工做方式略有不一樣。對於託管對象,有三次屬性值可能會發生變化:直接分配給它時; 當你[realm refresh]在另外一個線程上提交寫入事務後調用或自動刷新域當你[realm beginWriteTransaction]在另外一個線程上的更改後調用時,當前線程上的刷新沒有拾取這些更改。

在後兩種狀況下,將在另外一個線程上的寫入事務中進行的全部更改將當即應用,而且KVO通知將一次所有發送。任何中間步驟都將被丟棄,所以若是在寫入事務中將屬性從1增長到10,則在主線程上,您將直接從1到10得到一次更改通知。因爲屬性在不在寫入事務中或甚至在開始寫入事務時可能會更改值,-observeValueForKeyPath:ofObject:change:context:所以不建議嘗試從內部修改託管的Realm對象

NSMutableArray屬性不一樣,觀察對RLMArray屬性所作的更改不須要使用-mutableArrayValueForKey:,儘管支持與不使用Realm編寫的代碼兼容。相反,您能夠直接調用修改方法RLMArray,而且將通知任何觀察其存儲的屬性的人。

在咱們的例子應用,您能夠找到使用領域具備很短的例子ReactiveCocoa從Objective-C中,並從斯威夫特ReactKit

加密

請注意咱們許可證的出口合規部分,由於若是您位於有美國出口限制或禁運的國家/地區,它會對使用Realm進行限制。

Realm支持在建立Realm時經過提供64字節加密密鑰,使用AES-256 + SHA2加密磁盤上的數據庫文件。

// Generate a random encryption key NSMutableData *key = [NSMutableData dataWithLength:64]; (void)SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes); // Open the encrypted Realm file RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.encryptionKey = key; NSError *error = nil; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error]; if (!realm) { // If the encryption key is wrong, `error` will say that it's an invalid database NSLog(@"Error opening realm: %@", error); } // Use the Realm as normal RLMResults<Dog *> *dogs = [Dog objectsInRealm:realm where:@"name contains 'Fido'"];

這使得存儲在磁盤上的全部數據均可以根據須要使用AES-256進行透明加密和解密,並使用SHA-2 HMAC進行驗證。每次得到Realm實例時都必須提供相同的加密密鑰。

請參閱咱們的加密示例應用程序,瞭解生成加密密鑰的端到端應用程序,將其安全地存儲在鑰匙串中,並使用它來加密領域。

使用加密領域時,性能受到很小影響(一般低於10%)。

使用同步領域

您是否但願使用Realm Mobile Platform同步全部Realm數據庫?全部與同步相關的文檔已移至咱們的平臺文檔中

穿線

領域讀取事務生存期與RLMRealm實例的內存生存期相關聯避免經過使用自動刷新領域「固定」舊的Realm事務,並在顯式自動釋放池中包含全部使用Realm API的後臺線程。

有關此效果的更多詳細信息,請參閱咱們的當前限制

在單個線程中,您能夠將全部內容視爲常規對象,而無需擔憂併發或多線程。不須要任何鎖定或資源協調來訪問它們(即便它們同時在其餘線程上被修改),而且它只修改必須包含在寫入事務中的操做。

經過確保每一個線程始終具備一致的Realm視圖,Realm使併發使用變得容易。您能夠在同一個Realms上並行處理任意數量的線程,而且由於它們都有本身的快照,因此它們永遠不會致使彼此看到不一致的狀態。

您惟一須要注意的是,您不能讓多個線程共享相同的Realm對象實例若是多個線程須要訪問相同的對象,則每一個線程都須要獲取本身的實例(不然在一個線程上發生的更改可能會致使其餘線程看到不完整或不一致的數據)。

查看其餘線程的更改

在主UI線程(或任何具備runloop的線程)上,對象將在runloop的每次迭代之間自動更新來自其餘線程的更改。在任何其餘時間,您將處理快照,所以各個方法始終能夠看到一致的視圖,而沒必要擔憂其餘線程上發生的狀況。

當您最初在線程上打開Realm時,其狀態將基於最近成功的寫入提交,而且它將保留在該版本上直到刷新。除非將RLMRealmautorefresh屬性設置爲,不然在每次runloop迭代開始時都會自動刷新域NO若是一個線程沒有runloop(後臺線程一般就是這種狀況),那麼-[RLMRealm refresh]必須手動調用,以便將事務推動到最近的狀態。

提交寫入事務時,域也會刷新(-[RLMRealm commitWriteTransaction])。

未能按期刷新Realms可能致使某些事務版本變爲「固定」,從而阻止Realm重用該版本使用的磁盤空間,從而致使更大的文件大小。

跨線程傳遞實例

RLMObjects的非託管實例與常規NSObject子類徹底相同,而且能夠安全地傳遞線程。

的實例RLMRealmRLMResults或者RLMArray,託管實例或者RLMObject線程限制,這意味着它們只能在建立它們的線程上使用,不然會拋出異常*。這是Realm強制執行事務版本隔離的一種方式。不然,當在沒有可能普遍的關係圖的狀況下在不一樣事務版本的線程之間傳遞對象時,將沒法肯定應該作什麼。

Realm公開了一種機制,能夠經過三個步驟安全地傳遞線程限制的實例:

  1. 使用RLMThreadSafeReference線程限制對象初始化a 
  2. 將其傳遞RLMThreadSafeReference到目標線程或隊列。
  3. 經過調用在目標Realm上解析此引用-[RLMRealm resolveThreadSafeReference:]像往常同樣使用返回的對象。
Person *person = [Person new]; person.name = @"Jane"; [realm transactionWithBlock:^{ [realm addObject:person]; }]; RLMThreadSafeReference *personRef = [RLMThreadSafeReference referenceWithThreadConfined:person]; dispatch_async(queue, ^{ @autoreleasepool { RLMRealm *realm = [RLMRealm realmWithConfiguration:realm.configuration error:nil]; Person *person = [realm resolveThreadSafeReference:personRef]; if (!person) { return; // person was deleted } [realm transactionWithBlock:^{ person.name = @"Jane Doe"; }]; } });

一個RLMThreadSafeReference對象必須最多一次能夠解決。未能解析RLMThreadSafeReference將致使Realm的源版本被固定,直到引用被取消分配。出於這個緣由,RLMThreadSafeReference應該是短暫的。

能夠從任何線程訪問這些類型的一些屬性和方法:

  • RLMRealm:全部屬性,類方法和初始化程序。
  • RLMObjectisInvalidatedobjectSchemarealm,類方法,並初始化。
  • RLMResultsobjectClassNamerealm
  • RLMArrayisInvalidatedobjectClassName,和realm

跨線程使用領域

要從不一樣的線程訪問同一個Realm文件,您必須初始化一個新的Realm,以便爲您的應用程序的每一個線程獲取不一樣的實例。只要指定相同的配置,全部Realm實例都將映射到磁盤上的同一文件。

支持跨線程共享Realm實例訪問同一Realm文件的Realm實例也必須所有使用相同的Realm實例RLMRealmConfiguration

經過在單個事務中將多個突變批處理在一塊兒編寫大量數據時,域能夠很是高效。也可使用Grand Central Dispatch在後臺執行事務,以免阻塞主線程。RLMRealm對象不是線程安全的,不能跨線程共享,所以您必須在要讀取或寫入的每一個線程/調度隊列中獲取Realm實例。如下是在後臺隊列中插入一百萬個對象的示例:

dispatch_async(queue, ^{ @autoreleasepool { // Get realm and table instances for this thread RLMRealm *realm = [RLMRealm defaultRealm]; // Break up the writing blocks into smaller portions // by starting a new transaction for (NSInteger idx1 = 0; idx1 < 1000; idx1++) { [realm beginWriteTransaction]; // Add row via dictionary. Property order is ignored. for (NSInteger idx2 = 0; idx2 < 1000; idx2++) { [Person createInRealm:realm withValue:@{@"name" : randomString, @"birthdate" : randomDate}]; } // Commit the write transaction // to make this data available to other threads [realm commitWriteTransaction]; } } });

JSON

Realm沒有直接支持JSON,可是能夠RLMObject使用輸出來從JSON 添加[NSJSONSerialization JSONObjectWithData:options:error:]生成的符合KVC的對象可用於RLMObject使用標準API添加/更新以建立和更新對象。

// A Realm Object that represents a city @interface City : RLMObject @property NSString *name; @property NSInteger cityId; // other properties left out ... @end @implementation City @end // None needed NSData *data = [@"{\"name\": \"San Francisco\", \"cityId\": 123}" dataUsingEncoding: NSUTF8StringEncoding]; RLMRealm *realm = [RLMRealm defaultRealm]; // Insert from NSData containing JSON [realm transactionWithBlock:^{ id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]; [City createOrUpdateModifiedInRealm:realm withValue:json]; }];

若是JSON中有嵌套對象或數組,它們將自動映射到一對多關係。有關更多詳細信息,請參閱嵌套對象部分。

使用此方法在Realm中插入或更新JSON數據時,請注意Realm指望JSON屬性名稱和類型與RLMObject屬性徹底匹配例如:

  • float應使用float-backed 初始化屬性NSNumbers
  • NSDateNSData屬性不能從字符串自動推斷,但應在傳遞以前轉換爲適當的類型[RLMObject createOrUpdateModifiedInRealm:withValue:]
  • 若是爲必需屬性提供了JSON null(即NSNull),則將引起異常。
  • 若是在插入時沒有爲必需屬性提供屬性,則將引起異常。
  • Realm將忽略未定義的JSON中的任何屬性RLMObject

若是您的JSON架構與Realm對象不徹底對齊,咱們建議您使用第三方模型映射框架來轉換您的JSON。Objective-C有一組蓬勃發展的主動維護模型映射框架,它與Realm一塊兒工做,其中一些列在realm-cocoa存儲庫中

測試和調試

配置默認域

使用和測試Realm應用程序的最簡單方法是使用默認的Realm爲了不在測試之間覆蓋應用程序數據或泄漏狀態,您只需將默認Realm設置爲每一個測試的新文件。

// A base class which each of your Realm-using tests should inherit from rather // than directly from XCTestCase @interface TestCaseBase : XCTestCase @end @implementation TestCaseBase - (void)setUp { [super setUp]; // Use an in-memory Realm identified by the name of the current test. // This ensures that each test can't accidentally access or modify the data // from other tests or the application itself, and because they're in-memory, // there's nothing that needs to be cleaned up. RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.inMemoryIdentifier = self.name; [RLMRealmConfiguration setDefaultConfiguration:config]; } @end

注入Realm實例

測試與Realm相關的代碼的另外一種方法是讓您要測試的全部方法都接受一個RLMRealm實例做爲參數,這樣您就能夠在運行應用程序和測試時傳入不一樣的Realms。例如,假設您的應用程序具備GET來自JSON API的用戶配置文件的方法,而且您但願測試是否正確建立了本地配置文件:

// Application Code @implementation ClassBeingTested + (void)updateUserFromServer { NSURL *url = [NSURL URLWithString:@"http://myapi.example.com/user"]; [[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { [self createOrUpdateUserInRealm:[RLMRealm defaultRealm] withData:data]; }] resume]; } + (void)createOrUpdateUserInRealm:(RLMRealm *)realm withData:(NSData *)data { id object = [NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingOptions)nil error:nil]; [realm transactionWithBlock:^{ [User createOrUpdateModifiedInRealm:realm withValue:object]; }]; } @end // Test Code @implementation UnitTests - (void)testThatUserIsUpdatedFromServer { RLMRealm *testRealm = [RLMRealm realmWithURL:kTestRealmURL]; NSData *jsonData = [@"{\"email\": \"help@realm.io\"}" dataUsingEncoding:NSUTF8StringEncoding]; [ClassBeingTested createOrUpdateUserInRealm:testRealm withData:jsonData]; User *expectedUser = [User new]; expectedUser.email = @"help@realm.io"; XCTAssertEqualObjects([User allObjectsInRealm:testRealm][0], expectedUser, @"User was not properly updated from server."); } @end

調試

Realm Studio

Realm Studio是咱們的首選開發人員工具,能夠輕鬆管理Realm數據庫和Realm平臺。使用Realm Studio,您能夠打開和編輯本地和同步的域,並管理任何Realm Object Server實例。它支持Mac,Windows和Linux。

Realm Studio

咱們的SDK包含一個LLDB腳本,該腳本增長了對在Xcode UI中檢查託管RLMObjectRLMResultsRLMArrays對象的支持,而不是僅顯示每一個屬性nil0

Xcode調試控制檯的屏幕截圖

注意:該腳本目前僅支持Objective-C。迅捷的支持正在進行中。

若是您使用Realm做爲動態框架,則須要確保您的單元測試目標能夠找到Realm。您能夠經過將父路徑添加Realm.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. NSDataNSString屬性不能容納超過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對象,可使用RLMThreadSafeReferenceAPI。閱讀有關Realm線程的更多信息。

楷模

Setter和getter:因爲Realm會覆蓋setter和getter直接由底層數據庫返回屬性,所以不能在對象上覆蓋它們。一個簡單的解決方法是建立新的,Realm忽略的屬性,能夠覆蓋其訪問,並能夠調用其餘setter / getter。

自動遞增屬性:在生成主鍵時,Realm沒有用於其餘數據庫中經常使用的線程安全/進程安全自動遞增屬性的機制。可是,在須要惟一自動生成值的大多數狀況下,沒必要具備連續的,連續的整數ID。惟一的字符串主鍵一般就足夠了。常見的模式是將默認屬性值設置[[NSUUID UUID] UUIDString]爲生成惟一的字符串ID。

自動遞增屬性的另外一個常見動機是保持插入順序。在某些狀況下,這能夠經過將對象附加到a RLMArray或使用createdAt默認值爲的屬性來實現[NSDate date]

-[NSPredicate evaluateWithObject:]拒絕Realm集合做爲非集合對象:因爲內部實現一些過分約束的檢查NSPredicate,一些NSPredicate的API與Realm的集合類型不兼容。例如,-[NSPredicate evaluateWithObject:]當子查詢謂詞嘗試迭代Realm集合時將拋出異常。Apple意識到了這個問題(rdar:// 31252694)。

若是您須要在應用程序中解決此問題,能夠集成PR#4770中的補丁RLMWorkaroundRadar31252694()在執行任何謂詞評估以前調用一次。

文件大小

領域讀取事務生存期與RLMRealm實例的內存生存期相關聯避免經過使用自動刷新領域「固定」舊的Realm事務,並在顯式自動釋放池中包含全部使用Realm API的後臺線程。

您應該指望Realm數據庫在磁盤上佔用的空間少於等效的SQLite數據庫。若是您的Realm文件比預期的要大得多,多是由於您有一個RLMRealm指的是數據庫中較舊版本的數據。

爲了給您一致的數據視圖,Realm只更新在運行循環迭代開始時訪問的活動版本。這意味着若是您從Realm讀取一些數據,而後在其餘線程上寫入Realm時在長時間運行的操做中阻塞該線程,則該版本永遠不會更新,而且Realm必須保留您的數據的中間版本可能實際上並不須要,致使每次寫入時文件大小增長。額外的空間最終將被將來的寫入重用,或者可能被壓縮 - 例如,經過設置shouldCompactOnLaunch或調用writeCopyToPath:error:爲避免此問題,您能夠致電invalidate告訴Realm您再也不須要到目前爲止從Realm中讀取的任何對象,這使咱們沒法跟蹤這些對象的中間版本。Realm將在下次訪問時更新到最新版本。

使用Grand Central Dispatch訪問Realm時,您可能也會看到此問題。當一個Realm在調度隊列的自動釋放池中結束時會發生這種狀況,由於這些池在執行代碼後可能不會耗盡一段時間。RLMRealm取消分配對象以前,不能重用Realm文件中的數據的中間版本要避免此問題,從分派隊列訪問Realm時應使用顯式自動釋放池。

使用Realm API初始化Swift屬性

若是您正在編寫Swift應用程序,則可使用Realm API初始化其值的屬性來定義應用程序的類和結構。例如:

class SomeSwiftType { let persons = RLMPerson.allObjects(in: RLMRealm.default()) // ... }

若是您確實定義了具備此類屬性的類型,則應注意,若是在完成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操做可能在運行時失敗(與任何其餘磁盤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中運行如下命令:

pod cache clean Realm
pod cache clean RealmSwift
pod deintegrate || rm -rf Pods
pod install --verbose
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中運行如下命令:

rm -rf Carthage
rm -rf ~/Library/Developer/Xcode/DerivedData
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()

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.objectClasses = @[Dog.class, Person.class]; RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
相關文章
相關標籤/搜索