持久化方案-Realm

1,106 阅读7分钟

Realm框架

realm.io/cn/

介绍:

  • realm是一个跨平台移动数据库引擎,支持iOS、OS X(Objective-C和Swift)以及Android

  • 核心数据引擎C++打造,并不是建立在SQLite之上的ORM, 是拥有独立的数据库存储引擎 - realm.io/cn/news/jp-…

  • 注解:ORM

    • 对象-关系映射(OBJECT/RELATIONALMAPPING,简称ORM)
    • ORM技术是在对象和关系之间提供了一条桥梁
  • 性能 比sqlite, coredata牛逼

  • 易用 相比于sqlite, coredata, 使用起来更加简单, 更易入门

  • 使用教程 realm.io/docs/objc/l…

  • 辅助工具

截屏2021-03-19 下午8.10.59.png 点击 release.zip 下载插件。 可存储模型对象

Realm实战

1. 简单的数据操作

  • 1.1准备: 创建数据模型, 继承自RLMObject必须
  • 创建对象的方式
    1. 普通创建
    2. 通过父类RLMObject中的方法快速创建
      initWithValue
      +字典 键值对
      +数组 跟属性顺序保持一致
    3. 注意
    • 请注意,所有的必需属性都必须在对象添加到 Realm 前被赋值
    • 由于Realm 在自己的引擎内部有很好的语义解释系统,所以 Objective‑C 的许多属性特性将被忽略,如nonatomic, atomic, strong, copy 和 weak 等。 因此为了避免误解,官方推荐在编写数据模型的时候不要使用任何的属性特性。
  • 1.2 使用RLMRealm对象, 保存指定模型
    1. 获取RLMRealm对象 RLMRealm *realm = [RLMRealm defaultRealm];
    2. 写入方式1 开启写入事务 [realm beginWriteTransaction]; 添加模型对象 [realm addObject:stu]; 提交写入事务 [realm commitWriteTransaction];
    3. 写入方式2 [realm transactionWithBlock:^{[realm addObject:stu];}];
    4. 写入方式3 [Stu createInRealm:realm withValue:@{@"stu_id": @22, @"name": @"马冬梅2", @"age": @666}];
  • 1.3 使用RLMRealm对象, 更新指定模型
    • 方式1 在事务中直接更新对象
    [realm beginWriteTransaction];
    stu.name = @"土豆";
    [realm commitWriteTransaction];
    
    • 方式2 根据主键进行更新
        1. 要求操作的模型, 必须实现方法+ (NSString *)primaryKey 返回主键
        1. 在事务中调用方法 [realm addOrUpdateObject:stu2];
    • 方式3 根据主键进行更新
        1. 要求操作的模型, 必须实现方法+ (NSString *)primaryKey 返回主键
        1. 在事务中调用方法 [Stu createInRealm:realm withValue:@{@"stu_id": @22, @"name": @"马冬梅2", @"age": @666}];
  • 1.4 使用RLMRealm对象, 删除数据
    • 删除指定的对象

      • 在事务中
        • [realm deleteObject:stu];
        • 注意: 必须是从realm数据库中获取的模型对象, 而不是自己创建的
        • RLMObject *obj = [realm objectWithClassName:@"Stu" forPrimaryKey:@2];
    • 删除所有对象 在事务中 [realm deleteAllObjects];

  • 1.5 使用RLMRealm对象, 查询数据
    • 注意事项
        1. 所有的查询(包括查询和属性访问)在 Realm 中都是延迟加载的,只有当属性被访问时,才能够读取相应的数据
        1. 查询结果并不是数据的拷贝:修改查询结果(在写入事务中)会直接修改硬盘上的数据。
        1. 一旦检索执行之后, RLMResults 将随时保持更新
    • 查询所有 [Stu allObjects]
    • 条件查询 RLMResults<Stu *> *stus = [Stu objectsWhere:@"name = '马冬梅'"];
    • 排序 [stus sortedResultsUsingProperty:@"name" ascending:YES];
    • 链式查询 含义 在查询结果的基础上, 进行二次查询 [stus objectsWhere:@"address beginswith '北京'"];
    • 分页
      • 注意:
        • 查询出来的结果对象是懒加载, 只有真正访问时, 才会加载相应对象, 所以, 这里的分页, 其实就是从所有集合中分页获取即可
        • 代码演示
        RLMResults<Dog *> *dogs = [Dog allObjects];  
            for (NSInteger i = 0; i < 5; i++) {
             Dog *dog = dogs[i];
          // ...
        }
        
  • 2.支持的数据类型 BOOL, bool, int, NSInteger, long, long long, float, double, NSString, NSDate, NSData, and NSNumber
    • 注意:不支持集合类型
    • 解决方案
      • 序列化成NSData进行存储
      • 转换成RLMArray进行存储
    • 实战案例 存储image
  • 3.关系
    • 对一关系 : 当一个对象持有另外一个对象时, 比如人有一个宠物🐶
    • 对多关系
        1. 在Dog中, 遵循指定协议方法
        • RLM_ARRAY_TYPE(Dog)
        • RLM_ARRAY_TYPE 宏创建了一个协议,从而允许 RLMArray 语法的使用。
        1. 在Person中, 定义属性
        • @property (nonatomic, strong) RLMArray<Dog *> *dogs;
        • 注意:虽然可以给 RLMArray 属性赋值为 nil,但是这仅用于“清空”数组,而不是用以移除数组。这意味着您总是可以向一个 RLMArray 属性中添加对象,即使其被置为了 nil。
    • 反向关系
      • 举例 :人拥有狗, 狗又有相应的主人?
      • 解决
          1. Dog中定义属性 @property (readonly) RLMLinkingObjects *master;
          1. 实现协议方法, 标明链接关系
        + (NSDictionary<NSString *,RLMPropertyDescriptor *> *)linkingObjectsProperties {
        return @{
        @"master": [RLMPropertyDescriptor descriptorWithClass:NSClassFromString(@"Stu") propertyName:@"dogs"]
            };
        }
        
    1. 可空属性&默认值&忽略属性
    • 默认情况下, 属性值可空, 如果强制要求某个属性非空, 可以使用如下方法
    • 遵循协议方法
    + (NSArray *)requiredProperties {
        return @[@"name"];
    }
    
    • 特点 如果再次赋值为nil, 则会抛出异常错误
    • 也可以设置默认值
    + (NSDictionary *)defaultPropertyValues {
        return @{@"name": @""};
    }
    
    • 忽略属性
      • 不想存储的某些属性
      • 实现方法 + (NSArray *)ignoredProperties
    • 开发经验 可以借助忽略属性&只读属性 打造计算属性, 完成集合以及UIImage对象的存储与获取
    1. 通知
    • Realm 实例将会在每次写入事务提交后,给其他线程上的 Realm 实例发送通知
      1. 获取 Realm 通知
    token = [realm addNotificationBlock:^(NSString *notification, RLMRealm * realm) {
            // 接收到更改通知, 需要做的事情
    }];
    
      1. 移除通知 [token stop]; 注意: 必须持有返回的token
    1. Realm数据库
    • 用户机制
      • 不同的用户, 使用不同的数据库文件
      • 实现方案
          1. 不同的用户, 使用不同的数据库
        + (void)setDefaultRealmForUser:(NSString *)username {
            RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
            // 使用默认的目录,但是使用用户名来替换默认的文件名
            config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]URLByAppendingPathComponent:username]URLByAppendingPathExtension:@"realm"];
            // 将这个配置应用到默认的 Realm 数据库当中
            RLMRealmConfiguration setDefaultConfiguration:config];
	}
- 只读方式打开数据库
 RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
	// 获取需要打包文件的 URL 路径
	config.fileURL = [[NSBundle mainBundle] URLForResource:@"MyBundledData" withExtension:@"realm"];
	// 以只读模式打开文件,因为应用数据包并不可写
	config.readOnly = YES;
	// 通过配置打开 Realm 数据库
	RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
	// 从打包的 Realm 数据库中读取某些数据
	RLMResults<Dog *> *dogs = [Dog objectsInRealm:realm where:@"age > 5"];
- 数据库文件删除
    注意:1. 需要删除数据库文件以及辅助文件
代码实战
NSFileManager *manager = [NSFileManager defaultManager];
				 RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
				 NSArray<NSURL *> *realmFileURLs = @[
				   config.fileURL,
				   [config.fileURL URLByAppendingPathExtension:@"lock"],
				   [config.fileURL URLByAppendingPathExtension:@"log_a"],
				   [config.fileURL URLByAppendingPathExtension:@"log_b"],
				   [config.fileURL URLByAppendingPathExtension:@"note"]
				 ];
				 for (NSURL *URL in realmFileURLs) {
				   NSError *error = nil;
				   [manager removeItemAtURL:URL error:&error];
				   if (error) {
				     // 处理错误
				   }
				 }
    1. 数据库迁移
    • 适用于修改了数据模型的情况
    • 数据结构迁移
    // 在 [AppDelegate didFinishLaunchingWithOptions:] 中进行配置
    RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
    // 设置新的架构版本。这个版本号必须高于之前所用的版本号(如果您之前从未设置过架构版本,那么这个版本号设置为 0)
    config.schemaVersion = 1;
    // 设置闭包,这个闭包将会在打开低于上面所设置版本号的 Realm 数据库的时候被自动调用
    config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
        // 目前我们还未进行数据迁移,因此 oldSchemaVersion == 0
        if (oldSchemaVersion < 1) {
        // 什么都不要做!Realm 会自行检测新增和需要移除的属性,然后自动更新硬盘上的数据库架构
        }
    };
    // 告诉 Realm 为默认的 Realm 数据库使用这个新的配置对象
    [RLMRealmConfiguration setDefaultConfiguration:config];
    // 现在我们已经告诉了 Realm 如何处理架构的变化,打开文件之后将会自动执行迁移
    [RLMRealm defaultRealm];
    
    • 数据迁移
    // enumerateObjects:block: 方法遍历了存储在 Realm 文件中的每一个“Person”对象
    [migration enumerateObjects:Person.className block:^(RLMObject *oldObject, RLMObject *newObject) {
        // 将名字进行合并,存放在 fullName 域中
        newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@",oldObject[@"firstName"],oldObject[@"lastName"]];
    }];
    
    • 属性重命名
    [migration renamePropertyForClass:Person.className oldName:@"yearsSinceBirth" newName:@"age"];
    
    • 多版本增量式迁移
      • 场景
  • 版本0
 // v0
 // @interface Person : RLMObject
 // @property NSString *firstName;
 // @property NSString *lastName;
 // @property int age;
 @end
  • 版本1
// v1
// @interface Person : RLMObject
// @property NSString *fullName; // 新属性
// @property int age;
// @end
  • 版本2
// v2
@interface Person : RLMObject
@property NSString *fullName;
@property NSString *email;   // 新属性
@property int age;
@end
  • 迁移核心代码
 // enumerateObjects:block: 遍历了存储在 Realm 文件中的每一个“Person”对象
[migration enumerateObjects:Person.className                    block:^(RLMObject *oldObject, RLMObject *newObject) {// 只有当 Realm 数据库的架构版本为 0 的时候,才添加 “fullName” 属性if (oldSchemaVersion < 1) {  newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@",                            oldObject[@"firstName"],
                            oldObject[@"lastName"]];
}

// 只有当 Realm 数据库的架构版本为 0 或者 1 的时候,才添加“email”属性
if (oldSchemaVersion < 2) {
  newObject[@"email"] = @"";
}
}];