iOS开发

iOS数据库之Realm实战

2019-01-23  本文已影响17人  Felix_

之前的开发中,数据存储一般放在UserDefaults中,需要的话也会存储在数据库中,常用的就是FMDBCoreData了,某天,竟然发现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等,详见代码,所有的数据创建完毕后如图:

FelixUser
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实例是可以实时刷新的,它会自动更新基础数据,这意味着你不需要重新获取结果就可以获取最新的数据信息。

所以,我们运行项目并点击添加数据按钮之后,就不需要自己去维护数据源了,新的数据添加后就可以展示在页面上,如图:


以上内容转载请注明出处,同时也请大家不吝你的关注和下面的赞赏
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

上一篇下一篇

猜你喜欢

热点阅读