iOS常用數(shù)據(jù)持久化

數(shù)據(jù)層一直是程序的核心結(jié)構(gòu)之一抗悍,在iOS開發(fā)過程中通常需要對數(shù)據(jù)進(jìn)行持久化緩存以保證在無網(wǎng)絡(luò)情況下打開App后進(jìn)行一些展示或緩存聊天記錄等,這時候就需要持久化數(shù)據(jù)脸狸。

什么是數(shù)據(jù)持久化

數(shù)據(jù)持久化就是將內(nèi)存中的數(shù)據(jù)模型轉(zhuǎn)換為存儲模型,以及將存儲模型轉(zhuǎn)換為內(nèi)存中的數(shù)據(jù)模型的統(tǒng)稱. 數(shù)據(jù)模型可以是任何數(shù)據(jù)結(jié)構(gòu)或?qū)ο竽P?存儲模型可以是關(guān)系模型最仑、XML、二進(jìn)制流等炊甲。cmp和Hibernate只是對象模型到關(guān)系模型之間轉(zhuǎn)換的不同實現(xiàn)泥彤。

常用的數(shù)據(jù)持久化

在項目開發(fā)中我最常使用的持久化方式有:

  • NSUserDefault
  • Plist文件存儲
  • NSKeyedArchiver 歸檔
  • SQLite3(數(shù)據(jù)庫)

NSFileManager

Plist 和 歸檔 使用文件操作存儲需要用到NSFileManager,創(chuàng)建一個工具類來對App的文件進(jìn)行操作

NSFileManager 是處理文件系統(tǒng)的 Foundation 框架的高級API卿啡。它抽象了 Unix 和 Finder 的內(nèi)部構(gòu)成全景,和 iCloud ubiquitous containers 一樣, 提供了創(chuàng)建牵囤,讀取,移動滞伟,拷貝以及刪除本地或者網(wǎng)絡(luò)驅(qū)動器上的文件或者目錄的方法揭鳞。

NSFileManager 是一個單例使用以下方法來獲得

 + (NSFileManager *)defaultManager

NSFileManager常用方法就不多提了這里需要提的一點是線程注意。
apple 中提到:

Threading Considerations

The methods of the shared NSFileManager object can be called from multiple threads safely. However, if you use a delegate to receive notifications about the status of move, copy, remove, and link operations, you should create a unique instance of the file manager object, assign your delegate to that object, and use that file manager to initiate your operations.

大概說一下意思就是共享的NSFileManager對象方法可以從多個線程調(diào)用是安全的梆奈∫俺纾可是,如果使用委托通知的狀態(tài)移動亩钟,復(fù)制乓梨,刪除等等操作鳖轰,應(yīng)該創(chuàng)建一個唯一實例并使用該實例來開始你的操作。

如使用NSFileManagerDelegate時最好創(chuàng)建實例來進(jìn)行操作

NSFileManager *fileManager = [[NSFileManager alloc] init];
fileManager.delegate = delegate;

NSURL *bundleURL = [[NSBundle mainBundle] bundleURL];
NSArray *contents = [fileManager contentsOfDirectoryAtURL:bundleURL
                               includingPropertiesForKeys:@[]
                               options:NSDirectoryEnumerationSkipsHiddenFiles 
                               error:nil];

for (NSString *filePath in contents) {
    [fileManager removeItemAtPath:filePath error:nil];
}

下面是我開發(fā)中一些常用的文件操作方法

// 創(chuàng)建一個存儲文件夾并獲取路徑
+ (NSString *)getDocumentPath
{
    static NSString *documentPath = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        documentPath = [[NSString alloc]initWithFormat:@"%@/%@/",[paths objectAtIndex:0],FOLDER_FILE_NAME];
        if (![[NSFileManager defaultManager] fileExistsAtPath:documentPath])
        {
            [[NSFileManager defaultManager] createDirectoryAtPath:documentPath
                                      withIntermediateDirectories:NO
                                                       attributes:nil
                                                            error:nil];
        }
    });
    
    return documentPath;
}

// 文件大小
+ (NSUInteger)getFileSizeAtFileName:(NSString*)fileName{
  NSString *path = [[VGFileManagerCommon getDocumentPath] stringByAppendingPathComponent:fileName];
    return [[[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil]fileSize];
}
// 文件是否存在
+ (BOOL)isFileExistAtPath:(NSString*)filePath
{
    BOOL isExist = NO;
    isExist = [[NSFileManager defaultManager] fileExistsAtPath:filePath];
    return isExist;
}

// 文件是否存在
+ (BOOL)isFileExistAtFileName:(NSString*)fileName
{
    NSString *path = [[VGFileManagerCommon getDocumentPath] stringByAppendingPathComponent:fileName];
    return [VGFileManagerCommon isFileExistAtPath:path];
}
// 刪除文件
+ (BOOL)deleteFileAtFileName:(NSString *)fileName
{
    NSFileManager   *fileMgr = [NSFileManager defaultManager];
    NSString *deletePath = [[VGFileManagerCommon getDocumentPath] stringByAppendingPathComponent:fileName];
    NSError *err = nil;
    [fileMgr removeItemAtPath:deletePath error:&err];
    return err != nil;
}

NSUserDefault

常用來存儲一個簡單的狀態(tài)如是否第一次登陸

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setBool:YES forKey:@"firstLogin"];
[userDefaults synchronize];

Plist文件存儲

Property List扶镀,屬性列表文件蕴侣,它是一種用來存儲串行化后的對象的文件。屬性列表文件的擴(kuò)展名為.plist 臭觉,plist文件的實質(zhì)為XML文件昆雀。

可以被序列化的類型只有如下幾種:

NSArray 
NSMutableArray
NSDictionary 
NSMutableDictionary
NSData 
NSMutableData
NSString 
NSMutableString
NSNumber
NSDate

在實際的開發(fā)中我一般存儲NSArray和NSDictonary
經(jīng)過使用后建議使用JSON 存儲 取出后轉(zhuǎn)成模型,避免增減字段的問題

開發(fā)中例子

- (void)fetchItemsWithCompletion:(VGPlistStoreFetchCompletionHandler)completion{
    NSDictionary *cacheJson = [self loadCache];
    if (completion) {
        completion(cacheJson);
    }
}

- (NSDictionary *)loadCache
{
    NSString *path = [self savePath];
    if ([VGFileManagerCommon isFileExistAtPath:path]) {
        return [NSDictionary dictionaryWithContentsOfFile:path];
    }
    return nil;
}

- (NSString *)savePath
{
    return [[VGFileManagerCommon getDocumentPath] stringByAppendingPathComponent:@"cache.plist"];
}

- (void)saveJSON:(NSDictionary *)json
{
    if (json) {
        NSString *path = [self savePath];
        [json writeToFile:path atomically:YES];
    }
}

NSKeyedArchiver 歸檔

歸檔只要遵循了NSCoding協(xié)議的對象都可以通過它實現(xiàn)序列化蝠筑。
平時項目中使用Mantle 來實現(xiàn)model層 Mantle已經(jīng)實現(xiàn)了NSCoding協(xié)議

Demo中例子直接存儲model 但建議還是把Model轉(zhuǎn)換成JSON Dictionary 來存儲load時再轉(zhuǎn)換成model

+ (instancetype)store
{
    return [[self alloc] init];
}

- (void)fetchItemsWithCompletion:(VGKeyedArchiverStoreFetchCompletionHandler)completion
{
    if ([VGFileManagerCommon isFileExistAtPath:[self savePath]]) {
        VGCacheModel *model = [self loadModel];
        completion(model);
    }
}

- (VGCacheModel *)loadModel
{
    return [NSKeyedUnarchiver unarchiveObjectWithFile:[self savePath]];
}

- (NSString *)savePath
{
    NSString *path = @"keyedArchiverStore.bin";
    return [[VGFileManagerCommon getDocumentPath] stringByAppendingPathComponent:path];
}

- (void)saveModel:(VGCacheModel *)Model
{
    BOOL success = [NSKeyedArchiver archiveRootObject:Model toFile:[self savePath]];
    if (!success) {
        NSLog(@"Save Failed");
    }
}

SQLite3(數(shù)據(jù)庫)

一般用來存儲大量的內(nèi)容并可單一的修改更新某一條緩存信息等狞膘。實際上SQLite是無類型的。即不管你在創(chuàng)表時指定的字段類型是什么什乙,存儲是依然可以存儲任意類型的數(shù)據(jù)挽封。而且在創(chuàng)表時也可以不指定字段類型。

開發(fā)中我一般使用FMDB第三方庫來進(jìn)行數(shù)據(jù)庫操作臣镣,demo中例子就使用FMDB實現(xiàn)辅愿。

// 創(chuàng)建數(shù)據(jù)庫
- (void)openDatabase
{
    NSString *filepath = [[VGFileManagerCommon getDocumentPath] stringByAppendingString:@"cache.db"];
    FMDatabase *db = [FMDatabase databaseWithPath:filepath];
    if ([db open])
    {
        self.db = db;
        NSString *sql = @"CREATE TABLE IF NOT EXISTS CACHE \
        (uid INTEGER PRIMARY KEY, \
        url TEXT, \
        title TEXT)";
        if (![self.db executeUpdate:sql])
        {
            NSLog(@"execute sql %@ error %@",sql,self.db.lastError);
        }
    }
    else
    {
        NSLog(@"open database failed %@",filepath);
    }
}

#pragma mark - public

- (NSArray <VGCacheModel *>*)fetchCacheModelWithLimit:(NSInteger)limit{
    __block NSArray *result = nil;
    NSString *sql = nil;
    if (limit) {
        sql = @"SELECT *FROM CACHE ORDER BY uid DESC LIMIT ?";
    }
    db_sync_safe(^{
        NSMutableArray <VGCacheModel *>*array = [NSMutableArray array];
        FMResultSet *rs = [self.db executeQuery:sql, @(limit)];
        while ([rs next]) {
            VGCacheModel *model = loadToDatabase(rs);
            [array addObject:model];
        }
        [rs close];
        result = array;
    });
    return result;
}


- (void)saveModels:(NSArray <VGCacheModel *>*)models{
    db_sync_safe(^{
        if ([models count]) {
            [self.db beginTransaction];
            for (VGCacheModel*model in models) {
                saveToDatabase(self.db, model);
            }
            [self.db commit];
        }
    });
}

- (void)updateModel:(VGCacheModel *)model{
    NSString *sql = @"UPDATE CACHE SET TITLE = ? WHRER uid = ?";
    db_async(^{
        if (![self.db executeUpdate:sql, model.title, model.uid]) {
            NSLog(@"update failed sql %@",sql);
        }
    });
}

#pragma mark - save & load
static inline VGCacheModel * loadToDatabase(FMResultSet *resultSet)
{
    NSInteger uid = [resultSet longLongIntForColumn:@"uid"];
    NSString *URL = [resultSet stringForColumn:@"url"];
    NSString *title = [resultSet stringForColumn:@"title"];
    
    VGCacheModel *model = [[VGCacheModel alloc] init];
    model.uid = uid;
    model.imageURL = URL;
    model.title = title;
    
    return model;
}

static inline void saveToDatabase(FMDatabase *db, VGCacheModel *model)
{
    NSString *sql = @"INSERT OR REPLACE INTO CACHE(uid, url, title) VALUES(?,?,?)";
    if(![db executeUpdate:sql,
        @(model.uid),
        model.imageURL,
        model.title]){
        NSLog(@"update failed sql %@",sql);
    }
}


#pragma mark - Queue
dispatch_queue_t cacheDatabaseQueue()
{
    static dispatch_queue_t queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        queue = dispatch_queue_create(databaseQueue, 0);
        dispatch_queue_set_specific(queue, kDatabaseQueueSpecificKey, (void *)kDatabaseQueueSpecificKey, NULL);
    });
    return queue;
}

typedef void(^dispatch_block)(void);
void db_sync_safe(dispatch_block block)
{
    if (dispatch_get_specific(kDatabaseQueueSpecificKey))
    {
        block();
    }
    else
    {
        dispatch_sync(cacheDatabaseQueue(), ^() {
            block();
        });
    }
}

void db_async(dispatch_block block){
    dispatch_async(cacheDatabaseQueue(), ^() {
        block();
    });
}

DEMO

文章中例子的完整代碼
Demo簡單的實現(xiàn)了Plist、NSKeyedArchiver 歸檔退疫、SQLite3(數(shù)據(jù)庫) 三種數(shù)據(jù)持久化
VGCacheDemo

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市褒繁,隨后出現(xiàn)的幾起案子亦鳞,更是在濱河造成了極大的恐慌,老刑警劉巖棒坏,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件燕差,死亡現(xiàn)場離奇詭異,居然都是意外死亡坝冕,警方通過查閱死者的電腦和手機徒探,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喂窟,“玉大人测暗,你說我怎么就攤上這事∧ピ瑁” “怎么了碗啄?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長稳摄。 經(jīng)常有香客問我稚字,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任胆描,我火速辦了婚禮瘫想,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘昌讲。我一直安慰自己国夜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布剧蚣。 她就那樣靜靜地躺著支竹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸠按。 梳的紋絲不亂的頭發(fā)上礼搁,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機與錄音目尖,去河邊找鬼馒吴。 笑死,一個胖子當(dāng)著我的面吹牛瑟曲,可吹牛的內(nèi)容都是我干的饮戳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼洞拨,長吁一口氣:“原來是場噩夢啊……” “哼扯罐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起烦衣,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤歹河,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后花吟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秸歧,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年衅澈,在試婚紗的時候發(fā)現(xiàn)自己被綠了键菱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡今布,死狀恐怖经备,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情部默,我是刑警寧澤弄喘,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站甩牺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏累奈。R本人自食惡果不足惜贬派,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一急但、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧搞乏,春花似錦波桩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至侍筛,卻和暖如春萤皂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背匣椰。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工裆熙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人禽笑。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓入录,卻偏偏與公主長得像,于是被迫代替她去往敵國和親佳镜。 傳聞我的和親對象是個殘疾皇子僚稿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容