本文由我們團隊的王瑞華童鞋撰寫。
資料持久化就是將記憶體中的資料模型轉換為儲存模型,以及將儲存模型轉換為記憶體中的資料模型的統稱。 資料模型可以是任何資料結構或物件模型,儲存模型可以是關係模型、XML、二進位制流等。在iOS開發中,有很多種資料持久化方案,本文主要介紹 plist檔案,NSKeyedArchiver,SQLite3,NSUserDefaults。
iOS的沙盒模型
iOS最為人所熟知的就是其沙盒模型,一個App的讀寫許可權只限於自己的沙盒目錄中。沙盒模型的好處有哪些?1. 安全,別的app無法修改你的程式。2. 保護隱私,別人app無法讀取你的程式。3.便於系統管理,一個app產生的內容都包含在自己的沙盒中,便於系統管理。
iOS 沙盒目錄結構如下
1 2 3 4 5 6 |
App Bundle, 如xxx.app 其實是一個目錄,裡面包含app二進位制資料以及資原始檔 Documents, 存放程式產生的文件資料 Library Caches Preferences tmp,臨時檔案目錄,目前我所知道的是下載檔案會在此檔案暫存,完成後進行處理 |
如果我們想獲取上面某個目錄的路徑,應該如何實現呢?
對於最常用的Documents目錄,iTunes同步該應用時會同步該資料夾內容,適合存放使用者重要資料
1 2 |
NSString *searchPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSLog(@"%@", searchPath); |
Library/Caches:iTunes不會同步該資料夾
Library/Preferences: iTunes同步該應用時會同步此資料夾中的內容,通常儲存應用的設定資訊。
1 2 |
NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; NSLog(@"%@", path); |
tmp:此目錄適合儲存應用中的一些臨時檔案,用完就刪除。
1 2 |
NSString *path = NSTemporaryDirectory(); NSLog(@"%@", path); |
NSFileManager
在實際專案開發中,我們一般會建立一個工具類來對app的檔案進行管理。
NSFileManager 提供一個類方法獲得一個單例。
1 |
+ (NSFileManager *)defaultManager |
下面是NSFileManager的常用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/*新建目錄,createIntermediates這個引數一般為YES, *表示如果目錄路徑中間的某個目錄不存在則建立之, *如果是NO的話,則要保證所建立目錄的父目錄都必須已經存在 */ - (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)error; // 獲取目錄下的所有檔案 - (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error; - (BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error; - (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error; - (BOOL)linkItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error; - (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error; |
plist 檔案
plist檔案的實質為XML檔案,很容易檢視到檔案資料型別。可以被plist檔案序列話的型別只有以下幾種:
- NSArray, NSMutableArray
- NSDictionary, NSMutableDictionary
- NSData, NSMutableData
- NSString, NSMutableString
- NSNumber
- NSDate
而在實際的專案中,我們一般是將NSDictionary或NSArray的物件儲存到檔案或者從檔案讀取成物件。NSDictionary和NSArray會直接寫成plist檔案。
1 |
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile; |
序列化可以通過兩種途徑來進行
- 使用資料物件自帶方法
123456789// 檔案的寫入NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;NSString *fileName = [path stringByAppendingPathComponent:@"test.plist"];NSDictionary *dict = @{@"one": @1,@"two": @2};[dict writeToFile:fileName atomically:YES];// 檔案的讀取NSDictionary *dictFF = [NSDictionary dictionaryWithContentsOfFile:fileName]; - 使用NSPropertyListSerialization類
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
NSDictionary *dict = @{@"one": @1, @"two": @2}; NSString *error; NSData *xmlData = [NSPropertyListSerialization dataFromPropertyList:dataDictionary format:NSPropertyListXMLFormat_v1_0 errorDescription:&error]; if(xmlData) { NSLog(@"No error creating XML data."); [xmlData writeToFile:fileName atomically:YES]; } else { if (error) { NSLog(@"error:%@", error); } } // 讀取 NSDictionary *dictFF = (NSDictionary *)[NSPropertyListSerialization propertyListWithData:[NSData dataWithContentsOfFile:fileName] options:0 format:NULL error:&error]; |
UserDefault
顧名思義,它就是一個使用者的一些偏好設定。對於該部分,每一個app都要一個plist檔案專門儲存偏好設定資料。 plist檔名預設是程式Bundle identifier,副檔名為plist。除了程式自己的設定外,系統還有一些全域性的或者其它的一些設定,也屬於User Defaults的範疇,User Defaults的持久化資料都儲存在 ~/Library/Preferences 目錄中。NSUserDefaults 類來讀寫Preferences設定,而無需考慮檔案位置等細節問題。NSUserDefaults 用起來和 NSDictionary 很相似,多了一個Domain的概念在裡面。
1 2 3 |
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setBool:YES forKey:@"firstLogin"]; [userDefaults synchronize]; |
NSKeyedArchiver 歸檔
歸檔在iOS中是另一種形式的序列化,只要遵循了NSCoding協議的物件都可以通過它實現序列化。
遵從NSCoding協議
NSCoding 協議宣告瞭兩個方法,這兩個方法都是必須實現的。一個用於歸檔,一個用於解檔。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
@interface PhotoInfo : CBModel @property (nonatomic, copy) NSString *url; @property (nonatomic, copy) NSString *photoUrl; @property (nonatomic, assign) int width; @property (nonatomic, assign) int height; @end - (instancetype)initWithCoder:(NSCoder *)decoder { if (self = [super init]) { self.url = [decoder decodeObjectForKey:@"url"]; self.width = [[decoder decodeObjectForKey:@"width"] intValue]; self.height = [[decoder decodeObjectForKey:@"height"] intValue]; self.photoUrl = [decoder decodeObjectForKey:@"photoUrl"]; } return self; } - (void)encodeWithCoder:(NSCoder *)encoder { [encoder encodeObject:self.url forKey:@"url"]; [encoder encodeObject:@(self.width) forKey:@"width"]; [encoder encodeObject:@(self.height) forKey:@"height"]; [encoder encodeObject:self.photoUrl forKey:@"photoUrl"]; } [NSKeyedArchiver archiveRootObject:object toFile:file]; [NSKeyedUnarchiver unarchiveObjectWithFile:file]; |
SQLite3
由於本人對於資料庫的操作一直採用FMDB庫進行操作,一下就已該庫操作為例。
SQLite是無型別的。即不管你在創表時指定的欄位型別是什麼,儲存是依然可以儲存任意型別的資料。但為了良好的程式設計規範和增加可讀性,一般都指定型別。一般分為:
- integer : 整數 主鍵必須設定為該型別
- real : 實數(浮點數)
- text : 文字字串
- blob : 二進位制資料,比如檔案,圖片之類的
FMDB
FMDB是iOS平臺的SQLite資料庫框架,FMDB以OC的方式封裝了SQLite的C語言API。具有一下有點
- 使用起來更加物件導向,省去了很多麻煩、冗餘的C語言程式碼
- 對比蘋果自帶的Core Data框架,更加輕量級和靈活
- 提供了多執行緒安全的資料庫操作方法,有效地防止資料混亂
核心類包括FMDatabase, FMResultSet, FMDatabaseQueue這三個類。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// 建立表 NSString *sqlCreateTalentHistoryTable = [NSString stringWithFormat: @"create table if not exists testDB\n" "(pid INTEGER PRIMARY KEY AUTOINCREMENT,\n" "nickName text,\n" "avatarUrl text,\n" "isAdd text\n)" ]; BOOL resTalentHistory = [self.db executeUpdate:sqlCreateTalentHistoryTable]; if (!resTalentHistory) { NSLog(@"error when creating CBWallet_TalentHistory table"); } } // 執行更新 使用executeUpdate:方法執行更新 - (BOOL)executeUpdate:(NSString*)sql, ... - (BOOL)executeUpdateWithFormat:(NSString*)format, ... - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments 示例 [db executeUpdate:@"UPDATE testDB SET isAdd = ? WHERE nickName = ?;", @YES, @"Zhao"] // 執行查詢 查詢方法 - (FMResultSet *)executeQuery:(NSString*)sql, ... - (FMResultSet *)executeQueryWithFormat:(NSString*)format, ... - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments 示例 FMResultSet *rs = [db executeQuery:@"SELECT * FROM testDB"]; // 遍歷結果集 while ([rs next]) { NSString *name = [rs stringForColumn:@"nickName"]; BOOL isAdd = [rs intForColumn:@"isAdd"]; } |
對於資料庫操作,談論比較多的就是執行緒安全問題。不要讓多個執行緒操作同一個庫。為此,FMDB實現了一個FMDatabaseQueue類,以保證資料庫的執行緒安全。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 建立佇列 FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath]; // 使用佇列 [queue inDatabase:^(FMDatabase *database) { [database executeUpdate:@"INSERT INTO testDB(nickName, isAdd) VALUES (?, ?)", @"Zhang", @YES]; [database executeUpdate:@"INSERT INTO testDB(nickName, isAdd) VALUES (?, ?)", @"Wang", @YES]; [database executeUpdate:@"INSERT INTO testDB(nickName, isAdd) VALUES (?, ?)", @"Lee", @YES]; FMResultSet *result = [database executeQuery:@"select * from testDB"]; while([result next]) { } }]; // 也可以輕鬆地把簡單任務包裝到事務裡: [queue inTransaction:^(FMDatabase *database, BOOL *rollback) { [database executeUpdate:@"INSERT INTO testDB(nickName, isAdd) VALUES (?, ?)", @"Zhang", @YES]; [database executeUpdate:@"INSERT INTO testDB(nickName, isAdd) VALUES (?, ?)", @"Zhang", @YES]; [database executeUpdate:@"INSERT INTO testDB(nickName, isAdd) VALUES (?, ?)", @"Zhang", @YES]; FMResultSet *result = [database executeQuery:@"select * from testDB"]; while([result next]) { } }]; |
以上內容為本人根據工作中涉及的知識點整理內容,如有侵權請與我聯絡。