Realm 小记

记录 Realm 入门级操作。

创建 Realm 数据库文件

Realm 默认会在 Documents 文件下创建一个default.realm文件。

但是考虑到,用户必须要登录到 Web 后端服务器中,并且需要支持账户快速切换功能的话。 那么可以为每个账户提供一个独立的 Realm 数据库,以此区分不同账户本地数据,并且当前账户所使用的数据库将作为默认 Realm 数据库来使用。(应用场景:1.QQ iOS 客户端 本地文件;2.聊天记录)

Realm 数据库文件创建是懒加载,所以只需要事先配置好目录和文件名即可。

1
2
3
4
5
6
7
8
9
10
11
12
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// 使用默认的目录,但是请将文件名替换为用户名
config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]
URLByAppendingPathComponent:@"测试"]
URLByAppendingPathExtension:@"realm"];
// 将该配置设置为默认 Realm 配置
[RLMRealmConfiguration setDefaultConfiguration:config];
// Realm 懒加载,所以调用时才会创建
// ... ...

删除 Realm 数据库文件

在某些情况下,例如清除缓存、或者重置整个数据集之类的操作,那么就可能需要从磁盘中将 Realm 文件给完全删除掉。

尽管不是必须的,不过您应当将 Realm 辅助文件连同 Realm 文件一起删除,以完全清除所有的相关文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NSFileManager *manager = [NSFileManager defaultManager];
// 获取当前默认的 Realm 数据库
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) {
// 错误处理
}
}

数据模型

Realm 支持下述属性类型:BOOLboolintNSIntegerlonglong longfloatdoubleNSStringNSDateNSData 以及 被特殊类型标记的 NSNumber

CGFloat 属性被取消了,因为它不具备平台独立性。

主键

重写 +primaryKey 可以设置模型的主键。声明主键允许对象的查询和更新更加高效,并且会强制要求每个值保持唯一性。一旦将带有主键的对象添加到 Realm 数据库,那么该对象的主键将无法更改。

1
2
3
4
5
6
7
8
9
10
@interface Person : RLMObject
@property NSInteger id;
@property NSString *name;
@end
@implementation Person
+ (NSString *)primaryKey {
return @"id";
}
@end

属性特性

Realm 将会忽略诸如 nonatomicatomicstrongcopyweak 之类的 Objective-C 属性特性。这些特性对于 Realm 存储机制而言并没有意义。Realm 有自己优化过的存储语义。所以为了避免有人对代码产生误解,我们建议您在编写模型时不要附加任何属性特性。

属性备忘单

类型 非可空值形式 可空值形式
Bool @property BOOL value; @property NSNumber<RLMBool> *value;
Int @property int value; @property NSNumber<RLMInt> *value;
Float @property float value; @property NSNumber<RLMFloat> *value;
Double @property double value; @property NSNumber<RLMDouble> *value;
String @property NSString *value; @property NSString *value;
Data @property NSData *value; @property NSData *value;
Date @property NSDate *value; @property NSDate *value;
Object 不存在:必须是可空值 @property Object *value;
List @property RLMArray<Class *><Class> *value; 不存在:必须是非可空值
LinkingObjects @property (readonly) RLMLinkingObjects<Object *> *value; 不存在:必须是非可空值

Realm 关系构建

可以通过 RLMObjectRLMArray 属性来关联 RLMObjectRLMArray 的接口与 NSArray 非常类似,并且 RLMArray 当中的对象可以通过索引下标来进行访问。与 NSArray 所不同的是,RLMArray 的类型是固定的,并且 只能存放一种 RLMObject 类型。

多对一关系(包括一对一)

要配置多对一或者一对一关系,在数据模型当中声明一个 RLMObject 子类类型的属性即可:

1
2
3
4
5
6
// Dog.h
@interface Dog : RLMObject
// ... 其余属性声明
@property Person *owner;
@property Father *father;
@end

操作关系属性的方法与其他属性类似:

1
2
3
4
5
Person *jim = [[Person alloc] init];
Dog *rex = [[Dog alloc] init];
Father *frank = [[Father alloc] init];
rex.owner = jim;
rex.father = frank;

由此将 Person、Father 与 Dog 建立了对应关系。

在使用 RLMObject 属性时,可以使用正常的属性访问语法来访问嵌套属性。例如,rex.owner.address.country 将会遍历对象图,然后自动从 Realm 中检索出每个所需的对象。

多对多关系(包括一对多)

让我们给 Person 模型添加一个 dogs 属性,从而让其能够与多个 Dog 对象建立关系。首先,我们需要定义 RLMArray<Dog> 类型,也就是在 Dog 模型接口定义的底部使用这条宏:

1
2
3
4
5
6
// Dog.h
@interface Dog : RLMObject
// ... property declarations
@end
RLM_ARRAY_TYPE(Dog) // 定义 RLMArray<Dog> 类型

RLM_ARRAY_TYPE 宏创建了一个协议,从而允许您使用 RLMArray<Dog> 这种语法。如果这条宏没有放置在模型接口定义的底部,那么这个模型类就必须前置声明。

接下来,您就可以声明 RLMArray<Dog> 类型的属性了:

1
2
3
4
5
// Person.h
@interface Person : RLMObject
// ...其他属性声明
@property RLMArray<Dog *><Dog> *dogs;
@end

您可以照常对 RLMArray 属性进行访问和赋值:

1
2
3
4
// Jim 是 Rex 和所有名为 "Fido" 狗狗的主人
RLMResults<Dog *> *someDogs = [Dog objectsWhere:@"name contains 'Fido'"];
[jim.dogs addObjects:someDogs];
[jim.dogs addObject:rex];

双向关系

关系是单向的。以 PersonDog 这两个类为例。如果 Person.dogs 连接了一个 Dog 实例,那么您可以随着该连接从 Person 访问到对应的 Dog,但是是没有办法从 Dog 访问到对应的 Person 对象的。您可以设置一个一对一属性 Dog.owner 从而连接到 Person,但是这些连接实际上仍然是互相独立的。给 Person.dogs 添加一个 Dog 对象并不会将该对象的 Dog.owner 属性设置为对应的 Person。为了解决这个问题,Realm 提供了连接对象属性,从而表示这种双向关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
@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 属性,它包含所有 dogs 属性有该 Dog 对象的 Person 对象。将这个 owners 属性设置为 RLMLinkingObjects 类型,然后重写 +[RLMObject linkingObjectsProperties] 来表示 ownersPerson 模型对象之间的关系。

增删改查

创建对象

当定义完数据模型之后,就可以实例化 RLMObject 子类了, 然后还可以向 Realm 数据库中添加新的实例。以这个简单的数据模型为例:

1
2
3
4
5
6
7
8
9
// Dog 数据模型
@interface Dog : RLMObject
@property NSString *name;
@property NSInteger age;
@end
// Implementation
@implementation Dog
@end

创建新对象的方法有很多种:

1
2
3
4
5
6
7
8
9
10
// (1) 创建 Dog 对象,然后设置其属性
Dog *myDog = [[Dog alloc] init];
myDog.name = @"Rex";
myDog.age = 10;
// (2) 从字典中创建 Dog 对象
Dog *myOtherDog = [[Dog alloc] initWithValue:@{@"name" : @"Pluto", @"age" : @3}];
// (3) 从数组中创建 Dog 对象
Dog *myThirdDog = [[Dog alloc] initWithValue:@[@"Pluto", @3]];
  1. 使用指定初始化函数来创建对象是最直观的方式。请注意,所有非可空属性必须在对象添加到 Realm 数据库之前完成赋值。
  2. 通过恰当的键值,还可以使用字典来创建对象。
  3. 最后,RLMObject 的子类还可以使用数组来完成实例化。数组中的值必须与数据模型中对应的属性次序相同。

1
2
3
4
5
6
7
8
// 获取默认的 Realm 数据库
RLMRealm *realm = [RLMRealm defaultRealm];
// (每个线程)只需执行一次
// 在事务中向 Realm 数据库中添加数据
[realm beginWriteTransaction];
[realm addObject:myDog];
[realm commitWriteTransaction];

1
2
3
4
5
6
7
8
9
// 在事务中删除对象
[realm beginWriteTransaction];
[realm deleteObject:myDog];
[realm commitWriteTransaction];
// 从 Realm 数据库中删除所有对象
[realm beginWriteTransaction];
[realm deleteAllObjects];
[realm commitWriteTransaction];

1
2
3
4
// 在事务中更新对象
[realm beginWriteTransaction];
myDog.name = @"Thomas Pynchon";
[realm commitWriteTransaction];

Realm 条件查询方式与 NSPredicate 类似。

  • 比较操作数可以是属性名,也可以是常量。但至少要有一个操作数是属性名;

  • 比较操作符 ==<=<>=>!=BETWEEN 支持 intlonglong longfloatdouble以及 NSDate 这几种属性类型,例如 age == 45

  • 比较是否相同:==!=,例如,[Employee objectsWhere:@"company == %@", company]

  • 比较操作符 ==!= 支持布尔属性;

  • 对于 NSStringNSData 属性而言,支持使用 ==!=BEGINSWITHCONTAINSENDSWITH操作符,例如 name CONTAINS 'Ja'

  • 对于 NSString 属性而言,LIKE 操作符可以用来比较左端属性和右端表达式:?* 可用作通配符,其中 ? 可以匹配任意一个字符,* 匹配 0 个及其以上的字符。例如:value LIKE '?bc*' 可以匹配到诸如 “abcde” 和 “cbc” 之类的字符串;

  • 字符串的比较忽略大小写,例如 name CONTAINS[c] 'Ja'。请注意,只有 “A-Z” 和 “a-z” 之间的字符大小写会被忽略。[c] 修饰符可以与 [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 比较,例如 ANY student.age < 21

  • RLMArrayRLMResults 属性支持聚集表达式:@count@min@max@sum@avg,例如 [Company objectsWhere:@"employees.@count > 5"] 可用以检索所有拥有 5 名以上雇员的公司。

  • 支持子查询,不过存在以下限制:

  • @count 是唯一一个能在 SUBQUERY 表达式当中使用的操作符;
  • SUBQUERY(…).@count 表达式只能与常量相比较;
  • 目前仍不支持关联子查询。

关于谓词请阅读👉iOS中的谓词(NSPredicate)使用