iOS數(shù)據(jù)持久化方式
- 文件
- 歸檔(NSKeyedArchiver)
- 屬性列表(NSUserDefaults)
- 數(shù)據(jù)庫(SQLite、CoreData、第三方類庫)
一、文件
應(yīng)用程序包: 這里面存放的是應(yīng)用程序的源文件,包括資源文件和可執(zhí)行文件。NSString *path = [[NSBundle mainBundle] bundlePath];
/Documents:最常用的目錄,iTunes同步該應(yīng)用時會同步此文件夾中的內(nèi)容,適合存儲重要數(shù)據(jù)。NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
Library/Caches: iTunes不會同步此文件夾,適合存儲體積大,不需要備份的非重要數(shù)據(jù)。NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
Library/Preferences: iTunes同步該應(yīng)用時會同步此文件夾中的內(nèi)容,通常保存應(yīng)用的設(shè)置信息。NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
/tmp: iTunes不會同步此文件夾,系統(tǒng)可能在應(yīng)用沒運行時就刪除該目錄下的文件,所以此目錄適合保存應(yīng)用中的一些臨時文件,用完就刪除。NSString *path = NSTemporaryDirectory();
NSString *filePath = [[self getDocumentPath] stringByAppendingString:@"fileTest.txt"];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"798293@qq.com", @"email", @"787907@qq.com", @"emailDisplay", nil];
[dictionary writeToFile:filePath atomically:YES];
二、歸檔
歸檔(又名序列化),把對象轉(zhuǎn)為字節(jié)碼,以文件的形式存儲到磁盤上,程序運行過程中或者再次重新打開程序的時候,可以通過解歸檔(返序列化)還原這些對象。
- 歸檔的對象是Foundation框架中的對象
- 歸檔和解歸檔其中任意對象都需要歸檔和解歸檔整個文件
- 歸檔后的文件是加密的,所以歸檔文件的擴(kuò)展名可以隨意取
- 在帶鍵的歸檔中,每個歸檔都有一個key值,解歸檔時key值要與歸檔時key值匹配
- 如果一個自定義的類A,作為另一個自定義類B的一個屬性存在;那么,如果要對B進(jìn)行歸檔,那么,B要實現(xiàn)NSCoding協(xié)議。并且,A也要實現(xiàn)NSCoding協(xié)議
[NSKeyedArchiver archiveRootObject:obj toFile:appSettingPath];會調(diào)用對象的encodeWithCoder方法
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_name forKey:kAddressCardName];
[aCoder encodeObject:_emailObj forKey:kAddressCardEmail];
[aCoder encodeInteger:_salary forKey:kAddressCardSalary];
}
[NSKeyedUnarchiver unarchiveObjectWithFile:appSettingPath];會調(diào)用對象的initWithCoder方法
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
_name = [aDecoder decodeObjectForKey:kAddressCardName];
_emailObj = [aDecoder decodeObjectForKey:kAddressCardEmail];
_salary = [aDecoder decodeIntegerForKey:kAddressCardSalary];
return self;
}
三、屬性列表
NSUserDefaults適合存儲輕量級的本地數(shù)據(jù),一些簡單的數(shù)據(jù)(NSString類型的)例如密碼,網(wǎng)址等,NSUserDefaults肯定是首選,但是如果我們自定義了一個對象,對象保存的是一些信息,這時候就不能直接存儲到NSUserDefaults了。他的方便之處在于不用聲明太多的變量來存儲不同的數(shù)據(jù),一個NSUserDefaults就可以搞定,他是應(yīng)用程序域的,能讓我們進(jìn)行更加方便的使用。
原理:NSUserDefaults類提供了與默認(rèn)數(shù)據(jù)庫相交互的編程接口。其實它存儲在應(yīng)用程序的一個plist文件里,路徑為沙盒Document目錄平級的/Library/Prefereces里。如果將默認(rèn)數(shù)據(jù)庫比喻為SQL數(shù)據(jù)庫,那么NSUserDefaults就相當(dāng)于SQL語句。
NSUserDefaults支持的數(shù)據(jù)類型有:NSNumber(NSInteger、float、double),NSString,NSDate,NSArray,NSDictionary,BOOL,NSData
user defaults數(shù)據(jù)庫中其實是由多個層級的域組成的,當(dāng)你讀取一個鍵值的數(shù)據(jù)時,NSUserDefaults從上到下透過域的層級尋找正確的值,不同的域有不同的功能,有些域是可持久的,有些域則不行。
- 應(yīng)用域(application domain)是最重要的域,它存儲著你app通過NSUserDefaults set...forKey添加的設(shè)置。
- 注冊域(registration domain)僅有較低的優(yōu)先權(quán),只有在應(yīng)用域沒有找到值時才從注冊域去尋找。
- 全局域(global domain)則存儲著系統(tǒng)的設(shè)置
- 語言域(language-specific domains)則包括地區(qū)、日期等
- 參數(shù)域(argument domain)有最高優(yōu)先權(quán)
注意:
偏好設(shè)置是專門用來保存應(yīng)用程序的配置信息的,一般不要在偏好設(shè)置中保存其他數(shù)據(jù)。
如果沒有調(diào)用synchronize方法,系統(tǒng)會根據(jù)I/O情況不定時刻地保存到文件中。所以如果需要立即寫入文件的就必須調(diào)用synchronize方法。
偏好設(shè)置會將所有數(shù)據(jù)保存到同一個文件中。即preference目錄下的一個以此應(yīng)用包名來命名的plist文件。
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"jack" forKey:@"firstName"];
[defaults setInteger:10 forKey:@"Age"];
[defaults synchronize];
四、SQLite
SQLite數(shù)據(jù)庫的幾個特點:
- 基于C語言開發(fā)的輕型數(shù)據(jù)庫
- 在iOS中需要使用C語言語法進(jìn)行數(shù)據(jù)庫操作、訪問(無法使用ObjC直接訪問,因為libqlite3框架基于C語言編寫)
- SQLite中采用的是動態(tài)數(shù)據(jù)類型,即使創(chuàng)建時定義了一種類型,在實際操作時也可以存儲其他類型,但是推薦建庫時使用合適的類型(特別是應(yīng)用需要考慮跨平臺的情況時)
- 建立連接后通常不需要關(guān)閉連接(盡管可以手動關(guān)閉)
在iOS中操作SQLite數(shù)據(jù)庫可以分為以下幾個步驟:
- 打開數(shù)據(jù)庫,利用sqlite3_open()打開數(shù)據(jù)庫會指定一個數(shù)據(jù)庫文件保存路徑,如果文件存在則直接打開,否則創(chuàng)建并打開。打開數(shù)據(jù)庫會得到一個sqlite3類型的對象,后面需要借助這個對象進(jìn)行其他操作。
- 執(zhí)行SQL語句,執(zhí)行SQL語句又包括有返回值的語句和無返回值語句。
- 對于無返回值的語句(如增加、刪除、修改等)直接通過sqlite3_exec()函數(shù)執(zhí)行;
- 對于有返回值的語句則首先通過sqlite3_prepare_v2()進(jìn)行sql語句評估(語法檢測),然后通過sqlite3_step()依次取出查詢結(jié)果的每一行數(shù)據(jù),對于每行數(shù)據(jù)都可以通過對應(yīng)的sqlite3_column_類型()方法獲得對應(yīng)列的數(shù)據(jù),如此反復(fù)循環(huán)直到遍歷完成。當(dāng)然,最后需要釋放句柄。
打開數(shù)據(jù)庫
if(sqlite3_open(filePath.UTF8String, &_database) == SQLITE_OK)
{
NSLog(@"數(shù)據(jù)庫打開成功");
}
執(zhí)行無返回結(jié)果的SQL
if(sqlite3_exec(_database, sql.UTF8String, NULL, NULL, &error) != SQLITE_OK)
{
NSLog(@"執(zhí)行SQL語句過程中發(fā)生錯誤,錯誤信息:%s", error);
}
執(zhí)行有返回結(jié)果的SQL
sqlite3_stmt *stmt;
if(sqlite3_prepare_v2(_database, sql.UTF8String, -1, &stmt, NULL) == SQLITE_OK)
{
while(sqlite3_step(stmt) == SQLITE_ROW)
{
int columnCount = sqlite3_column_count(stmt);
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
for(int i = 0; i < columnCount; i++)
{
const char *name = sqlite3_column_name(stmt, i);
const unsigned char *value = sqlite3_column_text(stmt, i);
[dic setValue:[NSString stringWithUTF8String:(const char *)value] forKey:[NSString stringWithUTF8String:name]];
}
[rows addObject:dic];
}
sqlite3_finalize(stmt);
}
五、CoreData
Core Data是iOS5之后才出現(xiàn)的一個框架,它提供了對象-關(guān)系映射(ORM)的功能,即能夠?qū)C對象轉(zhuǎn)化成數(shù)據(jù),保存在SQLite數(shù)據(jù)庫文件中,也能夠?qū)⒈4嬖跀?shù)據(jù)庫中的數(shù)據(jù)還原成OC對象。在此數(shù)據(jù)操作期間,我們不需要編寫任何SQL語句
簡介
Core Data是個框架(并不是數(shù)據(jù)庫哦),它使開發(fā)者可以把數(shù)據(jù)當(dāng)做對象來操作,而不必在乎數(shù)據(jù)在磁盤中的存儲方式。對于iOS程序員來說,這很有用,因為我們已經(jīng)可以通過代碼非常熟悉的操作對象了。由Core Data 所提供的數(shù)據(jù)對象叫做托管對象(Managed Object),而Core Data本身則位于你的應(yīng)用程序和持久化存儲區(qū)(Persistent store)之間。為了把數(shù)據(jù)從托管對象映射到持久化存儲區(qū)中,Core Data 需要使用托管對象模型。所有的托管對象都必須位于托管對象上下文(Managed object context)里面,而托管對象上下文又位于高速的易失性存儲器里面,也就是位于RAM中。
為什么需要有托管對象上下文呢?原因之一就是在磁盤與RAM之間傳輸數(shù)據(jù)時會有開銷。磁盤讀寫速度比RAM慢的多,所以不應(yīng)該頻繁地訪問它。有了托管對象上下文,就可以非常迅速地獲取到了。但它的缺點在于,開發(fā)者必須在托管對象上下文上面定期調(diào)用save方法,以將變更后的數(shù)據(jù)寫回磁盤。托管對象上下文的另一個功能是記錄開發(fā)者對托管對象所對的修改,以提供完整的撤銷和重做支持。
上邊我們對Core Data簡單的介紹了一下。接下來我們需要對CoreData中的重要的名詞做一解釋。
持久化存儲協(xié)調(diào)器NSPersistentStoreCoordinator
持久化存儲協(xié)調(diào)器(persistent store coordinator)里面包含一份持久化存儲區(qū),而存儲區(qū)里面又含有數(shù)據(jù)表里面的若干行數(shù)據(jù)。設(shè)置持久化存儲協(xié)調(diào)器的時候,我們通常選用SQLite數(shù)據(jù)庫作為持久化存儲區(qū)。另外,也可以選用Binary、XML、或In-Memory等形式的持久化存儲區(qū)。但要注意的是,Binary和XML格式的存儲區(qū)是Atomic,也就是說,即便你只想修改少量的數(shù)據(jù),在保存的時候也依然需要把整個文件都寫入磁盤。
同一個持久化存儲協(xié)調(diào)器可以有多個持久化存儲區(qū)。把CoreData與iCloud相集成的時候,就可能會出現(xiàn)這樣的情況。我們可以把不屬于iCloud的數(shù)據(jù)放在一個存儲區(qū)里面,而把屬于iCloud的數(shù)據(jù)放在另外一個存儲區(qū)里面,這樣既能節(jié)省網(wǎng)絡(luò)寬帶,又能節(jié)省iCloud存儲空間。
即便你有兩個持久化存儲區(qū),也不意味著必須使用兩種對象圖。CoreData的模型配置允許開發(fā)者使用多個獨立的存儲區(qū),但卻采用同一套對象圖。在設(shè)置CoreData的模型配置選項時,可以指明對象圖里面的某一部分屬于哪一個持久化存儲區(qū)。
要想創(chuàng)建持久化存儲區(qū),需生成NSPersistentStore;要想創(chuàng)建持久化存儲協(xié)調(diào)器,需生成NSPersistentStoreCoordinator類的實例。
托管對象模型NSManagedObjectModel
托管對象模型它位于持久化存儲協(xié)調(diào)器和托管對象上下文之間。顧名思議,托管對象模型是描述數(shù)據(jù)結(jié)構(gòu)的模型或者圖示,而托管對象正是以它為基礎(chǔ)產(chǎn)生出來的??梢杂肵code來配置實體(Entity)及實體之間的關(guān)系。實體本身并不包含數(shù)據(jù),它們只是規(guī)定了基于該實體的托管對象具有何種特性。實體也有屬性,屬性的數(shù)據(jù)類型可以是整數(shù),字符串,或者日期等。
要想創(chuàng)建托管對象模型,需要生成NSManagedObjectModel類的實例
托管對象上下文NSManagedObjectContext
托管對象上下文中可包含多個托管對象。托管對象上下文負(fù)責(zé)管理其中對象的生命期,并且負(fù)責(zé)提供許多強(qiáng)大的功能。
托管對象上下文也可以不止有一個,有時我們需要在后臺處理任務(wù)(比方說把數(shù)據(jù)保存到磁盤或者導(dǎo)入數(shù)據(jù)),這種情況下可以采用多個上下文。加入在前臺上下文上面調(diào)用Save,那么用戶界面就可能會有卡頓現(xiàn)象,尤其當(dāng)數(shù)據(jù)變化較大的時候更是如此。要想避免這個問題,有個簡單的辦法就是只在用戶按下手機(jī)的Home鍵時才去調(diào)用Save,這時應(yīng)用程序會轉(zhuǎn)入到后臺。還有個稍微復(fù)雜的但卻很靈活的辦法,就是采取兩個托管對象上下文。請記住,托管對象上下文是存放在高速內(nèi)存里面的。你可以配置其中一個上下文,那么就可以將后臺上下文中的數(shù)據(jù)異步存入磁盤。這種分段式的做法可以確保磁盤寫入操作不會影響用戶界面的流暢度。
要想創(chuàng)建托管對象上下文,需要生成NSManagedObjectContext類的實例
coreData簡單創(chuàng)建流程
模型文件操作
1.1 創(chuàng)建模型文件,后綴名為.xcdatamodeld。創(chuàng)建模型文件之后,可以在其內(nèi)部進(jìn)行添加實體等操作(用于表示數(shù)據(jù)庫文件的數(shù)據(jù)結(jié)構(gòu))
1.2 添加實體(表示數(shù)據(jù)庫文件中的表結(jié)構(gòu)),添加實體后需要通過實體,來創(chuàng)建托管對象類文件。
1.3 添加屬性并設(shè)置類型,可以在屬性的右側(cè)面板中設(shè)置默認(rèn)值等選項。(每種數(shù)據(jù)類型設(shè)置選項是不同的)
1.4 創(chuàng)建獲取請求模板、設(shè)置配置模板等。
1.5 根據(jù)指定實體,創(chuàng)建托管對象類文件(基于NSManagedObject的類文件)
實例化上下文對象
2.1 創(chuàng)建托管對象上下文(NSManagedObjectContext)
2.2 創(chuàng)建托管對象模型(NSManagedObjectModel)
2.3 根據(jù)托管對象模型,創(chuàng)建持久化存儲協(xié)調(diào)器(NSPersistentStoreCoordinator)
2.4 關(guān)聯(lián)并創(chuàng)建本地數(shù)據(jù)庫文件,并返回持久化存儲對象(NSPersistentStore)
2.5 將持久化存儲協(xié)調(diào)器賦值給托管對象上下文,完成基本創(chuàng)建。
// 從應(yīng)用程序包中加載模型文件
NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
// 傳入模型對象,初始化持久化存儲協(xié)調(diào)器
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
// 構(gòu)建SQLite數(shù)據(jù)庫文件的路徑
NSString *docs = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSURL *url = [NSURL fileURLWithPath:[docs stringByAppendingString:@"person"]];
// 添加持久化存儲器,用sqlite作為存儲庫
NSError *error = nil;
NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error];
if(store == nil)
{
[NSException raise:@"添加數(shù)據(jù)庫錯誤" format:@"%@", [error localizedDescription]];
}
// 創(chuàng)建托管對象上下文
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
context.persistentStoreCoordinator = psc;
NSManagedObject *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:context];
[card setValue:@"4768558865" forKey:@"no"];
[person setValue:card forKey:@"card"];
// 利用上下文對象,將數(shù)據(jù)同步到持久化存儲庫
NSError *errorSave = nil;
BOOL sucess = [context save:&errorSave];
查詢數(shù)據(jù)
NSFetchRequest
在執(zhí)行fetch操作前,可以給NSFetchRequest設(shè)置一些參數(shù),這些參數(shù)包括謂詞、排序等條件,下面是一些基礎(chǔ)的設(shè)置。
設(shè)置查找哪個實體,從數(shù)據(jù)庫的角度來看就是查找哪張表,通過fetchRequestWithEntityName:或初始化方法來指定表名。
通過NSPredicate類型的屬性,可以設(shè)置查找條件,這個屬性在開發(fā)中用得最多。NSPredicate可以包括固定格式的條件以及正則表達(dá)式。
通過sortDescriptors屬性,可以設(shè)置獲取結(jié)果數(shù)組的排序方式,這個屬性是一個數(shù)組類型,也就是可以設(shè)置多種排序條件。(但是注意條件不要沖突)
通過fetchOffset屬性設(shè)置從查詢結(jié)果的第幾個開始獲取,通過fetchLimit屬性設(shè)置每次獲取多少個。主要用于分頁查詢,后面會講。
MOC執(zhí)行fetch操作后,獲取的結(jié)果是以數(shù)組的形式存儲的,數(shù)組中存儲的就是托管對象。NSFetchRequest提供了參數(shù)resultType,參數(shù)類型是一個枚舉類型。通過這個參數(shù),可以設(shè)置執(zhí)行fetch操作后返回的數(shù)據(jù)類型。
- NSManagedObjectResultType: 返回值是NSManagedObject的子類,也就是托管對象,這是默認(rèn)選項
- NSManagedObjectIDResultType: 返回NSManagedObjectID類型的對象,也就是NSManagedObject的ID,對內(nèi)存占用比較小。MOC可以通過NSManagedObjectID對象獲取對應(yīng)的托管對象,并且可以通過緩存NSManagedObjectID參數(shù)來節(jié)省內(nèi)存消耗
- NSDictionaryResultType: 返回字典類型對象
- NSCountResultType: 返回請求結(jié)果的count值,這個操作是發(fā)生在數(shù)據(jù)庫層級的,并不需要將數(shù)據(jù)加載到內(nèi)存中
NSPredicate
在iOS開發(fā)過程中,很多需求都需要用到過濾條件。例如過濾一個集合對象中存儲的對象,可以通過Foundation框架下的NSPredicate類來執(zhí)行這個操作。
CoreData中可以通過設(shè)置NSFetchRequest類的predicate屬性,來設(shè)置一個NSPredicate類型的謂詞對象當(dāng)做過濾條件。通過設(shè)置這個過濾條件,可以只獲取符合過濾條件的托管對象,不會將所有托管對象都加載到內(nèi)存中。這樣是非常節(jié)省內(nèi)存和加快查找速度的,設(shè)計一個好的NSPredicate可以優(yōu)化CoreData搜索性能。
// 從數(shù)據(jù)庫查詢數(shù)據(jù)
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context];
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO];
request.sortDescriptors = [NSArray arrayWithObject:sort];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like %@", @"MJ55*"];
request.predicate = predicate;
// 執(zhí)行請求
NSError *errorFetch = nil;
NSArray *objs = [context executeFetchRequest:request error:&errorFetch];
if(errorFetch)
{
[NSException raise:@"查詢錯誤" format:@"%@", [errorFetch localizedDescription]];
}
總結(jié)
NSPersistentStoreCoordinator有四種可選的持久化存儲方案,用得最多的是SQLite的方式。其中Binary和XML這兩種方式,在進(jìn)行數(shù)據(jù)操作時,需要將整個文件加載到內(nèi)存中,這樣對內(nèi)存的消耗是很大的。
- NSSQLiteStoreType : SQLite數(shù)據(jù)庫
- NSXMLStoreType : XML文件
- NSBinaryStoreType : 二進(jìn)制文件
- NSInMemoryStoreType : 直接存儲在內(nèi)存中
在coredata中所有的托管對象被創(chuàng)建出來后,都是關(guān)聯(lián)著context對象的,所以在對象進(jìn)行任何操作后,都會被記錄在context中,在最后調(diào)用context的save方法后,context會將操作交給coordinator去處理,coordinator將會將這個存儲任務(wù)指派給NSPersistentStore對象。