iOS数据库之Realm实战
之前的开发中,数据存储一般放在UserDefaults
中,需要的话也会存储在数据库中,常用的就是FMDB
和CoreData
了,某天,竟然发现Github
上面的Realm-Cocoa
星级竟然超过了MagicalRecord
,持平于FMDB
,闲暇之余对这个移动端数据库做了了解
移动端数据库Realm,是用来替代SQLite的一种解决方案,它有一套自己的数据库存储引擎,比SQLite更轻量级,拥有更快的速度,并且具有很多现代数据库的特性,比如支持JSON,流式api,数据变更通知,自动数据同步,简单身份验证,访问控制,事件处理,最重要的是跨平台,目前已有Java,Objective C,Swift,React-Native,Xamarin这五种实现。
Talk is cheap. Show me the code!!!!!!
废话不多说,需要了解Realm
相关概念和接口的这里请Realm官方文档
我们使用一个经典的商城数据库来展示下Realm
的使用,共有如下四个类
FelixUser
用户信息
FelixGoods
商品信息
FelixOrder
订单信息
FelixOrderDetails
订单详情
最终要达到的效果是
通过用户信息可以查询用户的订单
通过订单信息可以查看订单详情
通过商品详情信息可以查询用户购买的是什么商品、购买的数量、购买的价格等
通过商品信息查询哪个用户购买过该商品
通过商品信息查询哪个订单里有此商品
其他查询
那么,首先创建出我们的四个模型
@interface FelixUser : RLMObject
@property NSString *userID;
@property NSString *userName;
@property NSString *password;
@property NSString *nickName;
@property int age;
@property bool sex;
@property RLMArray<FelixOrder *><FelixOrder> *orderList;
@property RLMArray<FelixGoods *><FelixGoods> *goodsList;
@end
RLM_ARRAY_TYPE(FelixUser)
@implementation FelixUser
+ (NSString *)primaryKey{
return @"userID";
}
+ (NSDictionary *)defaultPropertyValues{
return @{@"userID":[[NSUUID UUID] UUIDString]};
}
+ (NSArray<NSString *> *)requiredProperties{
return @[@"userID",@"userName",@"password",@"nickName",@"age",@"sex"];
}
@end
@interface FelixOrder : RLMObject
@property NSString *orderID;
@property NSDate *createDate;
@property NSDate *updateDate;
@property float price;
@property (readonly) RLMLinkingObjects *user;
@property RLMArray<FelixOrderDetails *><FelixOrderDetails> *orderDetailses;
@property RLMArray<FelixGoods *><FelixGoods> *goodsList;
@end
RLM_ARRAY_TYPE(FelixOrder)
@implementation FelixOrder
+ (NSString *)primaryKey{
return @"orderID";
}
+ (NSDictionary *)defaultPropertyValues{
return @{@"orderID":[[NSUUID UUID] UUIDString]};
}
+ (NSDictionary<NSString *,RLMPropertyDescriptor *> *)linkingObjectsProperties{
return @{@"user":[RLMPropertyDescriptor descriptorWithClass:FelixUser.class propertyName:@"orderList"],
@"orderDetailsList":[RLMPropertyDescriptor descriptorWithClass:FelixUser.class propertyName:@"order"]
};
}
+ (NSArray<NSString *> *)requiredProperties{
return @[@"orderID",@"createDate",@"updateDate",@"price"];
}
@end
@interface FelixOrderDetails : RLMObject
@property NSString *orderDetailsID;
@property NSDate *createDate;
@property NSDate *updateDate;
@property (readonly) RLMLinkingObjects *order;
@property FelixGoods *goods;
@property RLMArray<FelixGoods *><FelixGoods> *goodsList;
@property int goodsCount;
@property float price;
@end
RLM_ARRAY_TYPE(FelixOrderDetails)
@implementation FelixOrderDetails
+ (NSString *)primaryKey{
return @"orderDetailsID";
}
+ (NSDictionary *)defaultPropertyValues{
return @{@"orderDetailsID":[[NSUUID UUID] UUIDString]};
}
+ (NSDictionary<NSString *,RLMPropertyDescriptor *> *)linkingObjectsProperties{
return @{@"order":[RLMPropertyDescriptor descriptorWithClass:FelixOrder.class propertyName:@"orderDetailses"]};
}
+ (NSArray<NSString *> *)requiredProperties{
return @[@"orderDetailsID",@"createDate",@"updateDate",@"goodsCount",@"price"];
}
@end
@interface FelixGoods : RLMObject
@property NSString *goodsID;
@property int count;
@property float price;
@property NSString *title;
@property NSString *picture;
@property (readonly) RLMLinkingObjects *userList;
@property (readonly) RLMLinkingObjects *orderList;
@property (readonly) RLMLinkingObjects *orderDetailsList;
@end
RLM_ARRAY_TYPE(FelixGoods)
@implementation FelixGoods
+ (NSString *)primaryKey{
return @"goodsID";
}
+ (NSDictionary *)defaultPropertyValues{
return @{@"goodsID":[[NSUUID UUID] UUIDString]};
}
+ (NSDictionary<NSString *,RLMPropertyDescriptor *> *)linkingObjectsProperties{
return @{@"userList":[RLMPropertyDescriptor descriptorWithClass:FelixUser.class propertyName:@"goodsList"],
@"orderList":[RLMPropertyDescriptor descriptorWithClass:FelixOrder.class propertyName:@"goodsList"],
@"orderDetailsList":[RLMPropertyDescriptor descriptorWithClass:FelixOrderDetails.class propertyName:@"goodsList"]
};
}
+ (NSArray<NSString *> *)requiredProperties{
return @[@"goodsID",@"title",@"picture",@"count",@"price"];
}
@end
模型创建、关联信息不多做解释,可参阅官方文档,下面我们分别创建初始数据
[realm transactionWithBlock:^{
for (int i = 0; i < 100000; i++) {
FelixGoods *goods = [[FelixGoods alloc] init];
goods.price = i+1.f;
goods.count = 1000;
goods.title = [NSString stringWithFormat:@"商品%010d",i+1];
goods.picture = [NSString stringWithFormat:@"%010d.png",i+1];
[realm addObject:goods];
}
}];
[realm transactionWithBlock:^{
for (int i = 0; i < 100000; i++) {
FelixUser *user = [[FelixUser alloc] init];
user.userName = [NSString stringWithFormat:@"用户%010d",i+1];
user.password = @"password";
user.nickName = @"nickName";
user.age = 18;
user.sex = arc4random()%2 == 0 ? true:false;
[realm addObject:user];
}
}];
这里我们分别创建了10000个用户
和10000件商品
,接下来模拟用户的购买行为
[realm transactionWithBlock:^{
for (int i = 0; i < 10000; i++) {
FelixUser *user = [FelixUser objectsWhere:@"userName = %@",[NSString stringWithFormat:@"用户%010d",arc4random()%100000+1]].firstObject;
FelixOrder *order = [[FelixOrder alloc] init];
order.createDate = [NSDate date];
order.updateDate = [NSDate date];
float price = 0.f;
int count = arc4random()%10+1;
for (int j = 0; j < count; j++){
FelixOrderDetails *orderDetails = [[FelixOrderDetails alloc] init];
orderDetails.createDate = [NSDate date];
orderDetails.updateDate = [NSDate date];
FelixGoods *goods = [FelixGoods objectsWhere:@"price = %f",(float)(arc4random()%100000+1)].firstObject;
orderDetails.goods = goods;
orderDetails.goodsCount = arc4random()%5+1;
orderDetails.price = orderDetails.goods.price * orderDetails.goodsCount;
[orderDetails.goodsList addObject:goods];
[user.goodsList addObject:goods];
[order.goodsList addObject:goods];
price += orderDetails.price;
[order.orderDetailses addObject:orderDetails];
[realm addObject:orderDetails];
}
order.price = price;
[user.orderList addObject:order];
[realm addObject:order];
}
}];
这里,我们模拟了10000次购买行为
,随机挑选一名用户,然后创建订单,初始化订单信息,接下来,随机创建1-10个商品详情
,随机选择一个商品指配给订单详情,添加到用户的goodsList
,订单的goodsList
,订单详情的goodsList
等,详见代码,所有的数据创建完毕后如图:
FelixGoods
FelixOrder
FelixOrderDetails
使用官方的
Realm Brower
基本可以看出大部分的关系,接下来我们来一一实现上面的查询工作:
-
查询用户的所有订单信息
//通过用户ID查询用户
FelixUser *user = [FelixUser objectsWhere:@"userID = '9CC71FF3-B8CE-4A2A-90D3-4289969A1068'"].firstObject;
if (user){
NSLog(@"%@",user.userName);
NSLog(@"%@",user.orderList);
}
其中userID
生成方式是主键索引的UUID,具有唯一性,查找到用户后,分别打印用户名和用户的订单信息
用户0000000025
RLMArray<FelixOrder> <0x6000023c9950> (
[0] FelixOrder {
orderID = D506AE86-8E6B-4823-8A7A-D929411168AB;
createDate = 2019-01-22 02:26:13 +0000;
updateDate = 2019-01-22 02:26:13 +0000;
price = 149623;
orderDetailses = RLMArray<FelixOrderDetails> <0x6000023c8d80> (
[0] FelixOrderDetails {
orderDetailsID = EF13002E-0332-443E-99B4-CEC3BEF3A1D0;
createDate = 2019-01-22 02:26:13 +0000;
updateDate = 2019-01-22 02:26:13 +0000;
goods = FelixGoods {
goodsID = 6B51D843-7A38-4705-B4B1-7D6570DC8BA8;
count = 1000;
price = 18566;
title = 商品0000018566;
picture = 0000018566.png;
};
goodsList = RLMArray<FelixGoods> <0x6000023c8fc0> (
[0] <Maximum depth exceeded>
);
goodsCount = 2;
price = 37132;
},
[1] FelixOrderDetails {
orderDetailsID = 99960413-AF87-481A-BD53-C1DDB0A7B9F8;
createDate = 2019-01-22 02:26:13 +0000;
updateDate = 2019-01-22 02:26:13 +0000;
goods = FelixGoods {
goodsID = FB3B334B-2F4A-4FA2-8333-A3F9991AD985;
count = 1000;
price = 37497;
title = 商品0000037497;
picture = 0000037497.png;
};
goodsList = RLMArray<FelixGoods> <0x6000023c8240> (
[0] <Maximum depth exceeded>
);
goodsCount = 3;
price = 112491;
}
);
goodsList = RLMArray<FelixGoods> <0x6000023c94d0> (
[0] FelixGoods {
goodsID = 6B51D843-7A38-4705-B4B1-7D6570DC8BA8;
count = 1000;
price = 18566;
title = 商品0000018566;
picture = 0000018566.png;
},
[1] FelixGoods {
goodsID = FB3B334B-2F4A-4FA2-8333-A3F9991AD985;
count = 1000;
price = 37497;
title = 商品0000037497;
picture = 0000037497.png;
}
);
}
)
有个需要注意的地方,在我们仅仅打印用户名的情况下,Realm
仅查询用户信息,不会去关联查询用户的订单之类的信息,只有当我们.orderList
也就是getOrderList
的时候,才回去关联查询相关信息,尽可能的节省资源。
-
通过订单信息可以查看订单详情
//通过订单ID查询订单详情
FelixOrder *order = [FelixOrder objectsWhere:@"orderID = '3BF9A1B1-5ACA-4987-96DB-A63D33394C11'"].firstObject;
if (order){
NSLog(@"%@",order.orderDetailses);
}
逻辑与上方雷同,不做详细解释,直接查看输出结果
RLMArray<FelixOrderDetails> <0x600001a46760> (
[0] FelixOrderDetails {
orderDetailsID = A31D1D44-FFFE-4E13-8548-7F36F1DB2F0E;
createDate = 2019-01-22 02:25:31 +0000;
updateDate = 2019-01-22 02:25:31 +0000;
goods = FelixGoods {
goodsID = F0A7D187-960D-419F-A7CD-935E7E0175E9;
count = 1000;
price = 14858;
title = 商品0000014858;
picture = 0000014858.png;
};
goodsList = RLMArray<FelixGoods> <0x600001a46f40> (
[0] FelixGoods {
goodsID = F0A7D187-960D-419F-A7CD-935E7E0175E9;
count = 1000;
price = 14858;
title = 商品0000014858;
picture = 0000014858.png;
}
);
goodsCount = 1;
price = 14858;
},
[1] FelixOrderDetails {
orderDetailsID = BBB36CF6-36B0-4B38-9436-2EAD9A2B0DFF;
createDate = 2019-01-22 02:25:31 +0000;
updateDate = 2019-01-22 02:25:31 +0000;
goods = FelixGoods {
goodsID = 239E04AF-F8BE-4E7D-8063-2118093BE071;
count = 1000;
price = 27276;
title = 商品0000027276;
picture = 0000027276.png;
};
goodsList = RLMArray<FelixGoods> <0x600001a46fd0> (
[0] FelixGoods {
goodsID = 239E04AF-F8BE-4E7D-8063-2118093BE071;
count = 1000;
price = 27276;
title = 商品0000027276;
picture = 0000027276.png;
}
);
goodsCount = 1;
price = 27276;
}
)
此处大家可以忽略goodsList
。
-
通过商品详情信息可以查询用户购买的是什么商品、购买的数量、购买的价格等
//通过订单详情ID查询信息
FelixOrderDetails *orderDetails = [FelixOrderDetails objectsWhere:@"orderDetailsID = '5DD4EC9D-56FD-4AE2-BB54-499FBF18A587'"].firstObject;
if (orderDetails){
NSLog(@"%@",orderDetails);
}
这里我们可以查看具体购买的商品信息
FelixOrderDetails {
orderDetailsID = 5DD4EC9D-56FD-4AE2-BB54-499FBF18A587;
createDate = 2019-01-22 02:25:31 +0000;
updateDate = 2019-01-22 02:25:31 +0000;
goods = FelixGoods {
goodsID = F7501513-79E0-426E-8905-E8DB72A3CB7F;
count = 1000;
price = 21429;
title = 商品0000021429;
picture = 0000021429.png;
};
goodsList = RLMArray<FelixGoods> <0x600000f866d0> (
[0] FelixGoods {
goodsID = F7501513-79E0-426E-8905-E8DB72A3CB7F;
count = 1000;
price = 21429;
title = 商品0000021429;
picture = 0000021429.png;
}
);
goodsCount = 5;
price = 107145;
}
-
通过商品信息查询哪些用户购买过该商品
//通过商品查询哪些用户购买过此商品
FelixGoods *goods = [FelixGoods objectsWhere:@"goodsID = 'F7501513-79E0-426E-8905-E8DB72A3CB7F'"].firstObject;
if (goods){
for (FelixUser *user in goods.userList) {
NSLog(@"%@",user.userName);
}
}
输出信息
用户0000097339
-
通过商品信息查询哪个订单里有此商品
//通过商品查询哪些订单包含此商品
FelixGoods *goods = [FelixGoods objectsWhere:@"goodsID = 'F7501513-79E0-426E-8905-E8DB72A3CB7F'"].firstObject;
if (goods){
for (FelixOrder *order in goods.orderList) {
NSLog(@"%@",order.orderID);
}
}
输出信息
C2D79B69-C7E7-4CF6-B042-222ECEFEF538
增删改查并未完全列举出来,因为相对比较简单,下面,我们来看一下Realm
的一个特别实用的特性。我们创建一个列表用来展示数据,模型如下:
@interface DogModel : RLMObject
@property NSString *dogID;
@property NSString *name;
@property int age;
@end
@implementation DogModel
+ (NSString *)primaryKey{
return @"dogID";
}
+ (NSDictionary *)defaultPropertyValues{
return @{@"dogID":[[NSUUID UUID] UUIDString]};
}
@end
然后,创建一个列表用来展示DogModel
的信息,不同于以往的是,不再实用常用的@property (nonatomic,strong) NSMutableArray *dogs;
来作为数据源了,而是直接使用RLMResults
的数据库查询结果做数据源,有什么好处呢?我们来看代码:
@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>
@property (nonatomic,strong) UITableView *tableView;
@property (nonatomic,strong) RLMResults *dogs;
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.dogs = [DogModel allObjects];
self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 88, self.view.bounds.size.width, self.view.bounds.size.height - 88)];
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.tableView.rowHeight = 80;
[self.view addSubview:self.tableView];
UIButton *addBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 20, 70, 40)];
addBtn.tag = 100;
[addBtn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
[addBtn setTitle:@"增加数据" forState:UIControlStateNormal];
addBtn.titleLabel.textAlignment = NSTextAlignmentCenter;
addBtn.backgroundColor = [UIColor colorWithRed:46/255.0 green:97/255.0 blue:150/255.0 alpha:1];
addBtn.titleLabel.adjustsFontSizeToFitWidth = YES;
addBtn.titleLabel.minimumScaleFactor = 0.1;
[self.view addSubview:addBtn];
addBtn.layer.cornerRadius = 8;
UIButton *deleBtn = [[UIButton alloc] initWithFrame:CGRectMake(80, 20, 70, 40)];
deleBtn.tag = 200;
[deleBtn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
[deleBtn setTitle:@"删除数据" forState:UIControlStateNormal];
deleBtn.titleLabel.textAlignment = NSTextAlignmentCenter;
deleBtn.backgroundColor = [UIColor colorWithRed:46/255.0 green:97/255.0 blue:150/255.0 alpha:1];
deleBtn.titleLabel.adjustsFontSizeToFitWidth = YES;
deleBtn.titleLabel.minimumScaleFactor = 0.1;
[self.view addSubview:deleBtn];
deleBtn.layer.cornerRadius = 8;
}
- (void)btnClick:(UIButton *)sender{
static int i = 0;
if (sender.tag == 100){
//add
DogModel *dog = [[DogModel alloc] init];
dog.name = [NSString stringWithFormat:@"第%03d条狗",i];
dog.age = i+1;
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject:dog];
}];
[self.tableView reloadData];
i++;
}else if (sender.tag == 200){
//delete
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm deleteObject:self.dogs.lastObject];
}];
[self.tableView reloadData];
i--;
}
}
从上面的代码来看,只在页面初始化的时候取了一次数据,而在点击按钮的时候只是增加/删除了数据,并没有重新查询数据库而是直接刷新了页面,那么界面刷新后数据能够同步吗?结果是肯定的,这就是Realm
的一个特性,官方解释如下
RLMResults
实例是可以实时刷新的,它会自动更新基础数据,这意味着你不需要重新获取结果就可以获取最新的数据信息。
所以,我们运行项目并点击添加数据按钮之后,就不需要自己去维护数据源了,新的数据添加后就可以展示在页面上,如图:
以上内容转载请注明出处,同时也请大家不吝你的关注和下面的赞赏
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓