在iOS開發(fā)過程中较剃,不管是做什么應(yīng)用上渴,都會(huì)碰到數(shù)據(jù)保存的問題蝉稳。本地存儲(chǔ)對(duì)提高數(shù)據(jù)交互效率有著重大的意義破婆。本文總結(jié)一下數(shù)據(jù)存儲(chǔ)的幾種方式
在這之前我們需要先對(duì)應(yīng)用程序的文件目錄有所了解,請(qǐng)看下圖
應(yīng)用程序的文件目錄
Document :保存應(yīng)用運(yùn)行時(shí)生成的需要持久化的數(shù)據(jù)啄巧,iTunes同步設(shè)備時(shí)會(huì)備份該目錄寻歧。例如,游戲應(yīng)用可將游戲存檔保存在該目錄
Library/Caches :保存應(yīng)用運(yùn)行時(shí)生成的需要持久化的數(shù)據(jù)秩仆,iTunes同步設(shè)備時(shí)不會(huì)備份該目錄码泛。一般存儲(chǔ)體積大、不需要備份的非重要數(shù)據(jù)
Library/Preference: 保存應(yīng)用的所有偏好設(shè)置澄耍,iOS的Settings(設(shè)置)應(yīng)用會(huì)在該目錄中查找應(yīng)用的設(shè)置信息噪珊。iTunes同步設(shè)備時(shí)會(huì)備份該目錄
temp :保存應(yīng)用運(yùn)行時(shí)所需的臨時(shí)數(shù)據(jù)晌缘,使用完畢后再將相應(yīng)的文件從該目錄刪除。應(yīng)用沒有運(yùn)行時(shí)痢站,系統(tǒng)也可能會(huì)清除該目錄下的文件磷箕。iTunes同步設(shè)備時(shí)不會(huì)備份該目錄
總結(jié):
為了避免同步過程時(shí)間過長(zhǎng),你需要對(duì)應(yīng)用中使用的文件放在哪里做出選擇阵难。很大的數(shù)據(jù)文件岳枷,盡量放置在Caches目錄下,而不是Documents目錄下呜叫,Documents目錄下文件將做備份空繁,這樣會(huì)很耗時(shí)。
存儲(chǔ)方式介紹
- NSKeyedArchiver: 采用歸檔的形式來保存數(shù)據(jù)沙盒中怀偷;
- NSUserDefaults:偏好設(shè)置數(shù)據(jù)存到沙盒的Library/Preferences目錄(本質(zhì)是plist);
- Write寫入方式: 永久保存在磁盤中;
- SQLite :采用SQLite數(shù)據(jù)庫來存儲(chǔ)數(shù)據(jù)家厌。
1.NSKeyedArchiver:(歸檔)
采用歸檔的形式來保存數(shù)據(jù),該數(shù)據(jù)對(duì)象需要遵守NSCoding協(xié)議椎工,并且該對(duì)象對(duì)應(yīng)的類必須提供encodeWithCoder:和initWithCoder:方法饭于。
前一個(gè)方法告訴系統(tǒng)怎么對(duì)對(duì)象進(jìn)行編碼,而后一個(gè)方法則是告訴系統(tǒng)怎么對(duì)對(duì)象進(jìn)行解碼维蒙。
缺點(diǎn):
歸檔的形式來保存數(shù)據(jù)掰吕,只能一次性歸檔保存以及一次性解壓。所以只能針對(duì)小量數(shù)據(jù)颅痊,而且對(duì)數(shù)據(jù)操作比較笨拙殖熟,即如果想改動(dòng)數(shù)據(jù)的某一小部分,還是需要解壓整個(gè)數(shù)據(jù)或者歸檔整個(gè)數(shù)據(jù)斑响。
例如對(duì)Person對(duì)象歸檔保存菱属。
定義Person:
@interface Person:NSObject{//遵守NSCoding協(xié)議
NSString *name;//待歸檔類型
}
@implementation Person
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:name forKey:@"name"];
}
-(void)initWithCoder:(NSCoder *)aDecoder
{
name=[aDeCoder decodeObjectforKey:@"name"];
}
歸檔操作:
如果對(duì)Person對(duì)象name屬性歸檔保存,只需要NSCoder子類NSKeyedArchiver的方法archiveRootObject:toFile: 即可舰罚。
// 創(chuàng)建person對(duì)象
Person *person = [[Person alloc] init];
person.name = @"DNS";
// 獲取cache
NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
// 拼接文件全路徑
NSString *filePath = [cachePath stringByAppendingPathComponent:@"person.data"];
// 把自定義對(duì)象歸檔
[NSKeyedArchiver archiveRootObject:person toFile:filePath];
解檔操作:
同樣調(diào)用NSCoder子類NSKeyedArchiver的方法unarchiveRootObject:toFile: 即可
NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [cachePath stringByAppendingPathComponent:@"person.data"];
// 解檔
[NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
注: initWithCoder什么時(shí)候需要調(diào)用[super initWithCoder:]
? initWithCoder原理:只要解析文件就會(huì)調(diào)用纽门,xib,storyboard都是文件,因此只要解析這兩個(gè)文件营罢,就會(huì)調(diào)用initWithCoder赏陵。
? 因此如果在storyboard使用自定義view,重寫initWithCoder方法,一定要調(diào)用[super initWithCoder:]饲漾,因?yàn)橹挥邢到y(tǒng)才知道怎么解析storyboard蝙搔,如果沒有調(diào)用,就解析不了這個(gè)文件考传。
2.NSUserDefaults:(偏好設(shè)置吃型,本質(zhì)是plist)
用來保存應(yīng)用程序設(shè)置和屬性、用戶保存的數(shù)據(jù)伙菊。用戶再次打開程序或開機(jī)后這些數(shù)據(jù)仍然存在败玉。
NSUserDefaults可以存儲(chǔ)的數(shù)據(jù)類型包括:NSData敌土、NSString、NSNumber运翼、NSDate返干、NSArray、NSDictionary血淌。如果要存儲(chǔ)其他類型矩欠,則需要轉(zhuǎn)換為前面的類型,才能用NSUserDefaults存儲(chǔ)悠夯。
好處:1.不需要關(guān)心文件名
2.快速做鍵值對(duì)存儲(chǔ)壞處: 能及時(shí)存儲(chǔ)癌淮,需要做同步操作,把內(nèi)存中的數(shù)據(jù)同步到硬盤上
底層:就是封裝了一個(gè)字典
注:在iOS7之前沦补,默認(rèn)不會(huì)馬上跟硬盤同步
存儲(chǔ):
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:@"DNS" forKey:@"name"];
// 在iOS7之前乳蓄,默認(rèn)不會(huì)馬上把跟硬盤同步
// 同步操作
[userDefaults synchronize];
讀取:
NSString *name = [[NSUserDefaults standardUserDefaults] objectForKey:@"name"];
NSLog(@"%@",name);
3. Write寫入方式:永久保存在磁盤中。
具體方法為:
第一步:獲得文件即將保存的路徑:
//使用C函數(shù)NSSearchPathForDirectoriesInDomains來獲得沙盒中目錄的全路徑夕膀。
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
//該函數(shù)有三個(gè)參數(shù)虚倒,目錄類型、he domain mask产舞、布爾值魂奥。
//其中布爾值表示是否需要通過~擴(kuò)展路徑。而且第一個(gè)參數(shù)是不變的易猫,即為NSSearchPathDirectory 耻煤。
//在IOS中后兩個(gè)參數(shù)也是不變的,即為:NSUserDomainMask 和 YES准颓。
NSString *ourDocumentPath =[documentPaths objectAtIndex:0];
//還有一種方法是使用NSHomeDirectory函數(shù)獲得sandbox的路徑哈蝇。
NSString *sandboxPath = NSHomeDirectory();
//將Documents添加到sandbox路徑上
NSString *documentPath = [sandboxPath stringByAppendingPathComponent:@"Documents"];
//這兩者的區(qū)別就是:使用NSSearchPathForDirectoriesInDomains比在NSHomeDirectory后面添加Document更加安全。因?yàn)樵撐募夸浛赡茉谖磥戆l(fā)送的系統(tǒng)上發(fā)生改變攘已。
第二步:生成在該路徑下的文件:
//fileName就是保存文件的文件名
NSString *FileName=[documentDirectory stringByAppendingPathComponent:fileName];
第三步:往文件中寫入數(shù)據(jù):
//將NSData類型對(duì)象data寫入文件买鸽,文件名為FileName
[data writeToFile:FileName atomically:YES];
//最后從文件中讀出數(shù)據(jù):
NSData data=[NSData dataWithContentsOfFile:FileName options:0 error:NULL];
4. SQLite:采用數(shù)據(jù)庫來存儲(chǔ)數(shù)據(jù)。
SQLite (http://www.sqlite.org/docs.html) 是一個(gè)輕量級(jí)的關(guān)系數(shù)據(jù)庫贯被。iOS SDK很早就支持了SQLite,在使用時(shí)妆艘,只需要加入 libsqlite3.dylib 依賴以及引入 sqlite3.h 頭文件即可彤灶。但是,原生的SQLite API在使用上相當(dāng)不友好批旺,在使用時(shí)幌陕,非常不便。于是汽煮,開源社區(qū)中就出現(xiàn)了一系列將SQLite API進(jìn)行封裝的庫搏熄,而FMDB (https://github.com/ccgus/fmdb) 則是開源社區(qū)中的優(yōu)秀者棚唆。
什么是FMDB
FMDB是iOS平臺(tái)的SQLite數(shù)據(jù)庫框架
FMDB以O(shè)C的方式封裝了SQLite的C語言APIFMDB的優(yōu)點(diǎn)
使用起來更加面向?qū)ο螅∪チ撕芏嗦闊┬睦⑷哂嗟腃語言代碼
對(duì)比蘋果自帶的Core Data框架宵凌,更加輕量級(jí)和靈活
提供了多線程安全的數(shù)據(jù)庫操作方法,有效地防止數(shù)據(jù)混亂FMDB的github地址
https://github.com/ccgus/fmdb
二止后、核心類
FMDB有三個(gè)主要的類
FMDatabase
一個(gè)FMDatabase對(duì)象就代表一個(gè)單獨(dú)的SQLite數(shù)據(jù)庫
用來執(zhí)行SQL語句FMResultSet
使用FMDatabase執(zhí)行查詢后的結(jié)果集FMDatabaseQueue
用于在多線程中執(zhí)行多個(gè)查詢或更新瞎惫,它是線程安全的
三、打開數(shù)據(jù)庫
通過指定SQLite數(shù)據(jù)庫文件路徑來創(chuàng)建FMDatabase對(duì)象
//獲得數(shù)據(jù)庫文件的路徑 NSString *doc=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *fileName=[doc stringByAppendingPathComponent:@"student.sqlite"];
FMDatabase *db = [FMDatabase databaseWithPath:path];
if (![db open]) {
NSLog(@"數(shù)據(jù)庫打開失斠胫辍瓜喇!");
}
文件路徑有三種情況
具體文件路徑
如果不存在會(huì)自動(dòng)創(chuàng)建空字符串@""
會(huì)在臨時(shí)目錄創(chuàng)建一個(gè)空的數(shù)據(jù)庫
當(dāng)FMDatabase連接關(guān)閉時(shí),數(shù)據(jù)庫文件也被刪除nil
會(huì)創(chuàng)建一個(gè)內(nèi)存中臨時(shí)數(shù)據(jù)庫歉糜,當(dāng)FMDatabase連接關(guān)閉時(shí)乘寒,數(shù)據(jù)庫會(huì)被銷毀
四、執(zhí)行更新
在FMDB中匪补,除查詢以外的所有操作伞辛,都稱為“更新”
create、drop叉袍、insert始锚、update、delete等
使用executeUpdate:方法執(zhí)行更新
- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
示例
[db executeUpdate:@"UPDATE t_student SET age = ? WHERE name = ?;", @20, @"Jack"]
五喳逛、執(zhí)行查詢
查詢方法
- (FMResultSet *)executeQuery:(NSString*)sql, ...
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
示例
// 查詢數(shù)據(jù)
FMResultSet *rs = [db executeQuery:@"SELECT * FROM t_student"];
// 遍歷結(jié)果集
while ([rs next]) {
NSString *name = [rs stringForColumn:@"name"];
int age = [rs intForColumn:@"age"];
double score = [rs doubleForColumn:@"score"];
}
使用FMDatabaseQueue
線程安全:在多個(gè)線程中同時(shí)使用一個(gè)FMDatabase實(shí)例是不明智的∏瓢疲現(xiàn)在你可以為每個(gè)線程創(chuàng)建一個(gè)FMDatabase對(duì)象。 不要讓多個(gè)線程分享同一個(gè)實(shí)例润文,它無法在多個(gè)線程中同時(shí)使用姐呐。 若此,程序會(huì)時(shí)不時(shí)崩潰典蝌,或者報(bào)告異常,讓人崩潰曙砂。所以,不要初始化FMDatabase對(duì)象骏掀,然后在多個(gè)線程中使用鸠澈。
請(qǐng)使用 FMDatabaseQueue。以下是使用方法:
//首先創(chuàng)建隊(duì)列截驮。
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
//這樣使用笑陈。
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
FMResultSet *rs = [db executeQuery:@"select * from foo"];
while([rs next]) { … }}];
//像這樣,輕松地把簡(jiǎn)單任務(wù)包裝到事務(wù)里:
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];
[db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];
if (whoopsSomethingWrongHappened) { *rollback = YES; return; }
// etc… [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]]; }];
FMDatabaseQueue 后臺(tái)會(huì)建立系列化的G-C-D隊(duì)列葵袭,并執(zhí)行你傳給G-C-D隊(duì)列的塊涵妥。這意味著 你從多線程同時(shí)調(diào)用調(diào)用方法,GDC也會(huì)按它接收的塊的順序來執(zhí)行坡锡。