簡 注冊 登入 添加關注
作者 伯恩的遺産 2015.04.11 19:55* 寫了35249字,被2308人關注,獲得了1673個喜歡
我要永遠地記住你!(iOS中幾種資料持久化方案)
字數3344 閱讀10172 評論14 喜歡149
概論
所謂的持久化,就是将資料儲存到硬碟中,使得在應用程式或機器重新開機後可以繼續通路之前儲存的資料。在iOS開發中,有很多資料持久化的方案,接下來我将嘗試着介紹一下5種方案:
- plist檔案(屬性清單)
- preference(偏好設定)
- NSKeyedArchiver(歸檔)
- SQLite 3
- CoreData
沙盒
在介紹各種存儲方法之前,有必要說明以下沙盒機制。iOS程式預設情況下隻能通路程式自己的目錄,這個目錄被稱為“沙盒”。
1.結構
既然沙盒就是一個檔案夾,那就看看裡面有什麼吧。沙盒的目錄結構如下:
"應用程式包"
Documents
Library
Caches
Preferences
tmp
2.目錄特性
雖然沙盒中有這麼多檔案夾,但是沒有檔案夾都不盡相同,都有各自的特性。是以在選擇存放目錄時,一定要認真選擇适合的目錄。
-
: 這裡面存放的是應用程式的源檔案,包括資源檔案和可執行檔案。"應用程式包"
NSString *path = [[NSBundle mainBundle] bundlePath]; NSLog(@"%@", path);
-
: 最常用的目錄,iTunes同步該應用時會同步此檔案夾中的内容,适合存儲重要資料。Documents
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject; NSLog(@"%@", path);
-
: iTunes不會同步此檔案夾,适合存儲體積大,不需要備份的非重要資料。Library/Caches
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; NSLog(@"%@", path);
-
: iTunes同步該應用時會同步此檔案夾中的内容,通常儲存應用的設定資訊。Library/Preferences
-
: iTunes不會同步此檔案夾,系統可能在應用沒運作時就删除該目錄下的檔案,是以此目錄适合儲存應用中的一些臨時檔案,用完就删除。tmp
NSString *path = NSTemporaryDirectory(); NSLog(@"%@", path);
plist檔案
plist檔案是将某些特定的類,通過XML檔案的方式儲存在目錄中。
可以被序列化的類型隻有如下幾種:
NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
1.獲得檔案路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@"123.plist"];
2.存儲
NSArray *array = @[@"123", @"456", @"789"];
[array writeToFile:fileName atomically:YES];
3.讀取
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@"%@", result);
4.注意
- 隻有以上列出的類型才能使用plist檔案存儲。
- 存儲時使用
方法。 其中writeToFile: atomically:
表示是否需要先寫入一個輔助檔案,再把輔助檔案拷貝到目标檔案位址。這是更安全的寫入檔案方法,一般都寫YES。atomically
- 讀取時使用
方法。arrayWithContentsOfFile:
Preference
1.使用方法
//1.獲得NSUserDefaults檔案
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
//2.向檔案中寫入内容
[userDefaults setObject:@"AAA" forKey:@"a"];
[userDefaults setBool:YES forKey:@"sex"];
[userDefaults setInteger: forKey:@"age"];
//2.1立即同步
[userDefaults synchronize];
//3.讀取檔案
NSString *name = [userDefaults objectForKey:@"a"];
BOOL sex = [userDefaults boolForKey:@"sex"];
NSInteger age = [userDefaults integerForKey:@"age"];
NSLog(@"%@, %d, %ld", name, sex, age);
2.注意
- 偏好設定是專門用來儲存應用程式的配置資訊的,一般不要在偏好設定中儲存其他資料。
- 如果沒有調用
方法,系統會根據I/O情況不定時刻地儲存到檔案中。是以如果需要立即寫入檔案的就必須調用synchronize
方法。synchronize
- 偏好設定會将所有資料儲存到同一個檔案中。即preference目錄下的一個以此應用包名來命名的plist檔案。
NSKeyedArchiver
歸檔在iOS中是另一種形式的序列化,隻要遵循了NSCoding協定的對象都可以通過它實作序列化。由于決大多數支援存儲資料的Foundation和Cocoa Touch類都遵循了NSCoding協定,是以,對于大多數類來說,歸檔相對而言還是比較容易實作的。
1.遵循NSCoding協定
NSCoding
協定聲明了兩個方法,這兩個方法都是必須實作的。一個用來說明如何将對象編碼到歸檔中,另一個說明如何進行解檔來擷取一個新對象。
- 遵循協定和設定屬性
//1.遵循NSCoding協定 @interface Person : NSObject <NSCoding> //2.設定屬性 @property (strong, nonatomic) UIImage *avatar; @property (copy, nonatomic) NSString *name; @property (assign, nonatomic) NSInteger age; @end
- 實作協定方法
//解檔 - (id)initWithCoder:(NSCoder *)aDecoder { if ([super init]) { self.avatar = [aDecoder decodeObjectForKey:@"avatar"]; self.name = [aDecoder decodeObjectForKey:@"name"]; self.age = [aDecoder decodeIntegerForKey:@"age"]; } return self; } //歸檔 - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.avatar forKey:@"avatar"]; [aCoder encodeObject:self.name forKey:@"name"]; [aCoder encodeInteger:self.age forKey:@"age"]; }
-
特别注意
如果需要歸檔的類是某個自定義類的子類時,就需要在歸檔和解檔之前先實作父類的歸檔和解檔方法。即
和[super encodeWithCoder:aCoder]
方法;[super initWithCoder:aDecoder]
2.使用
- 需要把對象歸檔是調用NSKeyedArchiver的工廠方法
方法。archiveRootObject: toFile:
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"]; Person *person = [[Person alloc] init]; person.avatar = self.avatarView.image; person.name = self.nameField.text; person.age = [self.ageField.text integerValue]; [NSKeyedArchiver archiveRootObject:person toFile:file];
- 需要從檔案中解檔對象就調用NSKeyedUnarchiver的一個工廠方法
即可。unarchiveObjectWithFile:
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"]; Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file]; if (person) { self.avatarView.image = person.avatar; self.nameField.text = person.name; self.ageField.text = [NSString stringWithFormat:@"%ld", person.age]; }
3.注意
- 必須遵循并實作NSCoding協定
- 儲存檔案的擴充名可以任意指定
- 繼承時必須先調用父類的歸檔解檔方法
SQLite3
之前的所有存儲方法,都是覆寫存儲。如果想要增加一條資料就必須把整個檔案讀出來,然後修改資料後再把整個内容覆寫寫入檔案。是以它們都不适合存儲大量的内容。
1.字段類型
表面上SQLite将資料分為以下幾種類型:
- integer : 整數
- real : 實數(浮點數)
- text : 文本字元串
- blob : 二進制資料,比如檔案,圖檔之類的
實際上SQLite是無類型的。即不管你在創表時指定的字段類型是什麼,存儲是依然可以存儲任意類型的資料。而且在創表時也可以不指定字段類型。SQLite之是以什麼類型就是為了良好的程式設計規範和友善開發人員交流,是以平時在使用時最好設定正确的字段類型!主鍵必須設定成integer
2. 準備工作
準備工作就是導入依賴庫啦,在iOS中要使用SQLite3,需要添加庫檔案:libsqlite3.dylib并導入主頭檔案,這是一個C語言的庫,是以直接使用SQLite3還是比較麻煩的。
3.使用
-
建立資料庫并打開
操作資料庫之前必須先指定資料庫檔案和要操作的表,是以使用SQLite3,首先要打開資料庫檔案,然後指定或建立一張表。
/** * 打開資料庫并建立一個表 */ - (void)openDatabase { //1.設定檔案名 NSString *filename = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.db"]; //2.打開資料庫檔案,如果沒有會自動建立一個檔案 NSInteger result = sqlite3_open(filename.UTF8String, &_sqlite3); if (result == SQLITE_OK) { NSLog(@"打開資料庫成功!"); //3.建立一個資料庫表 char *errmsg = NULL; sqlite3_exec(_sqlite3, "CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)", NULL, NULL, &errmsg); if (errmsg) { NSLog(@"錯誤:%s", errmsg); } else { NSLog(@"創表成功!"); } } else { NSLog(@"打開資料庫失敗!"); } }
-
執行指令
使用
方法可以執行任何SQL語句,比如創表、更新、插入和删除操作。但是一般不用它執行查詢語句,因為它不會傳回查詢到的資料。sqlite3_exec()
/** * 往表中插入1000條資料 */ - (void)insertData { NSString *nameStr; NSInteger age; for (NSInteger i = ; i < ; i++) { nameStr = [NSString stringWithFormat:@"Bourne-%d", arc4random_uniform()]; age = arc4random_uniform() + ; NSString *sql = [NSString stringWithFormat:@"INSERT INTO t_person (name, age) VALUES('%@', '%ld')", nameStr, age]; char *errmsg = NULL; sqlite3_exec(_sqlite3, sql.UTF8String, NULL, NULL, &errmsg); if (errmsg) { NSLog(@"錯誤:%s", errmsg); } } NSLog(@"插入完畢!"); }
-
查詢指令
前面說過一般不使用
方法查詢資料。因為查詢資料必須要獲得查詢結果,是以查詢相對比較麻煩。示例代碼如下:sqlite3_exec()
- sqlite3_prepare_v2() : 檢查sql的合法性
- sqlite3_step() : 逐行擷取查詢結果,不斷重複,直到最後一條記錄
- sqlite3_coloum_xxx() : 擷取對應類型的内容,iCol對應的就是SQL語句中字段的順序,從0開始。根據實際查詢字段的屬性,使用sqlite3_column_xxx取得對應的内容即可。
- sqlite3_finalize() : 釋放stmt
/** * 從表中讀取資料到數組中 */ - (void)readData { NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:]; char *sql = "select name, age from t_person;"; sqlite3_stmt *stmt; NSInteger result = sqlite3_prepare_v2(_sqlite3, sql, -, &stmt, NULL); if (result == SQLITE_OK) { while (sqlite3_step(stmt) == SQLITE_ROW) { char *name = (char *)sqlite3_column_text(stmt, ); NSInteger age = sqlite3_column_int(stmt, ); //建立對象 Person *person = [Person personWithName:[NSString stringWithUTF8String:name] Age:age]; [mArray addObject:person]; } self.dataList = mArray; } sqlite3_finalize(stmt); }
4.總結
總得來說,SQLite3的使用還是比較麻煩的,因為都是些c語言的函數,了解起來有些困難。不過在一般開發過程中,使用的都是第三方開源庫
FMDB
,封裝了這些基本的c語言方法,使得我們在使用時更加容易了解,提高開發效率。
FMDB
1.簡介
FMDB
是iOS平台的SQLite資料庫架構,它是以OC的方式封裝了SQLite的C語言API,它相對于cocoa自帶的C語言架構有如下的優點:
- 使用起來更加面向對象,省去了很多麻煩、備援的C語言代碼
- 對比蘋果自帶的Core Data架構,更加輕量級和靈活
- 提供了多線程安全的資料庫操作方法,有效地防止資料混亂
注:FMDB的gitHub位址
2.核心類
FMDB有三個主要的類:
-
FMDatabase
一個FMDatabase對象就代表一個單獨的SQLite資料庫,用來執行SQL語句
-
FMResultSet
使用FMDatabase執行查詢後的結果集
-
FMDatabaseQueue
用于在多線程中執行多個查詢或更新,它是線程安全的
3.打開資料庫
和c語言架構一樣,FMDB通過指定SQLite資料庫檔案路徑來建立FMDatabase對象,但FMDB更加容易了解,使用起來更容易,使用之前一樣需要導入
sqlite3.dylib
。打開資料庫方法如下:
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.db"];
FMDatabase *database = [FMDatabase databaseWithPath:path];
if (![database open]) {
NSLog(@"資料庫打開失敗!");
}
值得注意的是,Path的值可以傳入以下三種情況:
- 具體檔案路徑,如果不存在會自動建立
- 空字元串@"",會在臨時目錄建立一個空的資料庫,當FMDatabase連接配接關閉時,資料庫檔案也被删除
- nil,會建立一個記憶體中臨時資料庫,當FMDatabase連接配接關閉時,資料庫會被銷毀
4.更新
在FMDB中,除查詢以外的所有操作,都稱為“更新”, 如:create、drop、insert、update、delete等操作,使用
executeUpdate:
方法執行更新:
//常用方法有以下3種:
- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
//示例
[database executeUpdate:@"CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)"];
//或者
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES(?, ?)", @"Bourne", [NSNumber numberWithInt:42]];
5.查詢
查詢方法也有3種,使用起來相當簡單:
- (FMResultSet *)executeQuery:(NSString*)sql, ...
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
查詢示例:
//1.執行查詢
FMResultSet *result = [database executeQuery:@"SELECT * FROM t_person"];
//2.周遊結果集
while ([result next]) {
NSString *name = [result stringForColumn:@"name"];
int age = [result intForColumn:@"age"];
}
6.線程安全
在多個線程中同時使用一個FMDatabase執行個體是不明智的。不要讓多個線程分享同一個FMDatabase執行個體,它無法在多個線程中同時使用。 如果在多個線程中同時使用一個FMDatabase執行個體,會造成資料混亂等問題。是以,請使用 FMDatabaseQueue,它是線程安全的。以下是使用方法:
- 建立隊列。
- 使用隊列
[queue inDatabase:^(FMDatabase *database) {
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_1", [NSNumber numberWithInt:1]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_2", [NSNumber numberWithInt:2]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_3", [NSNumber numberWithInt:3]];
FMResultSet *result = [database executeQuery:@"select * from t_person"];
while([result next]) {
}
}];
而且可以輕松地把簡單任務包裝到事務裡:
[queue inTransaction:^(FMDatabase *database, BOOL *rollback) {
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_1", [NSNumber numberWithInt:]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_2", [NSNumber numberWithInt:2]];
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_3", [NSNumber numberWithInt:3]];
FMResultSet *result = [database executeQuery:@"select * from t_person"];
while([result next]) {
}
//復原
*rollback = YES;
}];
FMDatabaseQueue 背景會建立系列化的G-C-D隊列,并執行你傳給G-C-D隊列的塊。這意味着 你從多線程同時調用調用方法,GDC也會按它接收的塊的順序來執行。
CoreData
詳見我的另一篇筆記:我要娶你做我的CoreData!
聲明:
- 以上内容屬于本人整理的筆記,如有錯誤的地方希望能告訴我,大家共同進步。
- 以上内容有些段落或語句可能是本人從其他地方Copy而來,如有侵權,請及時告訴我。
推薦拓展閱讀 著作權歸作者所有
您覺得這篇文章對你有幫助嗎?看我這麼用心,鼓勵一下呗~
¥ 打賞支援 喜歡 149 分享到微網誌 分享到微信 更多分享 ×
喜歡的使用者
- oking2016.08.24 20:36
- 小道蕭兮2016.08.24 18:23
- 海寵2016.08.23 01:24
- 潇湘wei雨2016.08.23 00:00
- 梅寒傲雪2016.08.21 03:39
- 曹賊v52016.08.18 22:00
- h705c2016.08.14 03:24
- yaya_pangdun2016.08.12 18:04
- 滿橙2016.08.12 16:32
- 阿_姣爺2016.08.05 22:44
- haohua2016.08.04 17:17
- 克爾蘇加德2016.08.03 13:27
- Pu_Jiyong2016.07.19 09:59
- LightReason2016.07.13 15:15
- 輕諾2016.07.09 17:09
14條評論 ( 按時間正序· 按時間倒序· 按喜歡排序 ) 添加新評論
火星的蝈蝈
2 樓 · 2015.07.25 13:47
寫的太好了,謝謝了,順便請教樓主一個問題,發的長代碼都顯示不全,都會在後面截取一部分,怎麼才能看到完整的代碼啊?
喜歡(0) 回複
伯恩的遺産
3 樓 · 2015.07.25 20:41
@9c68f084c4aa 在網頁上看,下面會顯示托動條,可以左右拖動的
喜歡(0) 回複
橡果736: @伯恩的遺産 正好有一樣的問題 謝謝了
回複 2015.10.19 00:11
GameMobile: 留着看
回複 2015.11.10 11:15 添加新回複
火星的蝈蝈
4 樓 · 2015.07.25 21:08
嗯嗯,知道了,太感謝了