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];
相关文章
相关标签/搜索