iOS開發 - 利用SQLite和歸檔實作一個完美的資料持久化方案
資料持久化方案,可能很多人能想到,SQLite、CoreData、各種方案。有利有弊。我想到了一個比較完美的解決方案。
補充
這個方案還可以使用 JSON序列化和反序列化來代替 NSCoding
要用到兩個第三方:
- 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 ,因為反歸檔的時候,你取出的是有這個字段的,但是卻沒有這個屬性去接收。