版权声明:欢迎大家积极分享!交流。关注我~ https://blog.csdn.net/qinqi376990311/article/details/81538142
iOS开发 - 利用SQLite和归档实现一个完美的数据持久化方案
数据持久化方案,可能很多人能想到,SQLite、CoreData、各种方案。有利有弊。我想到了一个比较完美的解决方案。
要用到两个第三方:
- MJExtension (主要为了实现 NSCoding 协议)
- FMDB (主要为了方便操作SQLite)
好,开始~现在创建一个 Model 遵循 NSCoding 协议 ,这里我以一个用户模型作为示例:
SCUser.h
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, UserSex) {
UserSexPrivary = 0, //保密
UserSexMale, //男
UserSexFemale //女
};
@interface SCUser : NSObject <NSCoding>
@property (nonatomic, copy) NSString *userId;
@property (nonatomic, copy) NSString *username;
@property (nonatomic, copy) NSString *mobile;
@property (nonatomic, copy) NSString *nickname;
@property (nonatomic, assign) UserSex sex;
@property (nonatomic, copy) NSString *avatar;
@end
SCUser.m
#import "SCUser.h"
@implementation SCUser
MJCodingImplementation
@end
MJCodingImplementation 这个宏,帮你实现了 NSCoding 协议,因此不需要再实现 initWithCoder: 和 encodeWithCoder: 了。
第一步已完成,是不是很简单啊~
然后再创建一个数据库管理类:
SCDatabaseTool.h
#import <Foundation/Foundation.h>
@interface SCDatabaseTool : NSObject
/**清除数据库*/
+ (void)clearDatabase;
+ (void)insertUser:(SCUser *)user;
+ (SCUser *)selectUserWithID:(NSString *)userId;
+ (NSArray<SCUser *> *)selectAllUsers;
+ (void)updateUser:(SCUser *)user;
+ (void)deleteUser:(SCUser *)user;
//如果需要别的模型,就接着往下写。每个需要持久化的模型都应该有 增、删、改、查 的基本方法。
@end
SCDatabaseTool.m
#import "SCDatabaseTool.h"
#import "FMDB.h"
@implementation SCDatabaseTool
+ (FMDatabaseQueue *)getCurrentQueue {
//数据库的文件名很重要,建议以 db_ 开头,后面跟上 userId,这样不同的用户登录,相互数据不影响。
NSString *fileName = [NSString stringWithFormat:@"db_%@.sqlite", [SCAccountManager defaultManager].account.Id];
NSString *DBPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:fileName];
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:DBPath];
[queue inDatabase:^(FMDatabase *db) {
[db open];
//表名的命名规范,以 t_ 开头,后面跟上名字。
[db executeUpdate:@"create table if not exists t_users (userId text primary key, content blob);"];
//重点就在这里!我们数据库只有两个字段,一个是主键,也就是 userId,一个是二进制数据内容。
[db close];
}];
return queue;
}
/**清除数据库*/
+ (void)clearDatabase {
[[self getCurrentQueue] inDatabase:^(FMDatabase *db) {
[db open];
[db executeUpdate:@"drop table t_users"];
[db close];
}];
}
#pragma mark - 用户相关
+ (void)insertUser:(SCUser *)user {
[[self getCurrentQueue] inDatabase:^(FMDatabase *db) {
[db open];
//插入数据的时候,利用归档,生成二进制数据,写入数据库
[db executeUpdate:@"insert into t_users(userId, content) values(?, ?)", user.userId, [NSKeyedArchiver archivedDataWithRootObject:user]];
[db close];
}];
}
+ (void)updateUser:(SCUser *)user {
[[self getCurrentQueue] inDatabase:^(FMDatabase *db) {
[db open];
//修改的话,直接覆盖之前的二进制数据
[db executeUpdate:@"update t_users set content = ? where userId = ?", [NSKeyedArchiver archivedDataWithRootObject:user], user.userId];
[db close];
}];
}
+ (void)deleteUser:(SCUser *)user {
[[self getCurrentQueue] inDatabase:^(FMDatabase *db) {
[db open];
[db executeUpdate:@"delete from t_users where userId = ?", user.userId];
[db close];
}];
}
+ (SCUser *)selectUserWithID:(NSString *)userId {
__block SCUser *user = nil;
[[self getCurrentQueue] inDatabase:^(FMDatabase *db) {
[db open];
//查询到一条记录,要返回模型,需要反归档。
FMResultSet *result = [db executeQuery:@"select * from t_users where userId = ?", userId];
while ([result next]) {
user = [NSKeyedUnarchiver unarchiveObjectWithData:[result dataForColumn:@"content"]];
break;
}
[db close];
}];
return user;
}
+ (NSArray<SCUser *> *)selectAllUsers {
NSMutableArray *resultArr = [NSMutableArray array];
[[self getCurrentQueue] inDatabase:^(FMDatabase *db) {
[db open];
FMResultSet *result = [db executeQuery:@"select * from t_users"];
while ([result next]) {
SCUser *user = [NSKeyedUnarchiver unarchiveObjectWithData:[result dataForColumn:@"content"]];
[resultArr addObject:user];
}
[db close];
}];
return [resultArr copy];
}
@end
如此,就完成了,是不是很简单呐~
总结
这么做的好处呢:
- 不用担心写错SQL语句,如果一个表中字段过多,还必须一一对应,多一个少一个就会报错。我们只有两个字段,一个主键一个二进制内容。
- 数据库的表 和 本地模型 不需要一一对应。如果模型有变动,不需要修改数据库的表。如果按照以前的纯SQLite的方法,比如 user 模型如果多了一个 address 属性,那么还要修改数据库表。很麻烦。但是用现在这个方式,我们什么都不用做~完美兼容!
- 数据安全。即使数据库文件被别人从沙盒中提取,也看不到内容。因为我们是利用 OC 的归档生成的二进制。
- 内容不限,你甚至可以存储图片、音频、视频,等各种二进制数据。
注意
- 上面第二条说到,如果添加属性,是没有任何问题的。但是,如果是删除某个属性,就最好先调用 +clearDatabase 把数据库清除了,即 dropTable,其实就是因为这个宏 MJCodingImplementation ,因为反归档的时候,你取出的是有这个字段的,但是却没有这个属性去接收。