天天看點

iOS開發 - 利用SQLite和歸檔實作一個完美的資料持久化方案iOS開發 - 利用SQLite和歸檔實作一個完美的資料持久化方案

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
           

如此,就完成了,是不是很簡單呐~

總結

這麼做的好處呢:

  1. 不用擔心寫錯SQL語句,如果一個表中字段過多,還必須一一對應,多一個少一個就會報錯。我們隻有兩個字段,一個主鍵一個二進制内容。
  2. 資料庫的表 和 本地模型 不需要一一對應。如果模型有變動,不需要修改資料庫的表。如果按照以前的純SQLite的方法,比如 user 模型如果多了一個 address 屬性,那麼還要修改資料庫表。很麻煩。但是用現在這個方式,我們什麼都不用做~完美相容!
  3. 資料安全。即使資料庫檔案被别人從沙盒中提取,也看不到内容。因為我們是利用 OC 的歸檔生成的二進制。
  4. 内容不限,你甚至可以存儲圖檔、音頻、視訊,等各種二進制資料。

注意

  • 上面第二條說到,如果添加屬性,是沒有任何問題的。但是,如果是删除某個屬性,就最好先調用 +clearDatabase 把資料庫清除了,即 dropTable,其實就是因為這個宏 MJCodingImplementation ,因為反歸檔的時候,你取出的是有這個字段的,但是卻沒有這個屬性去接收。