iOS 中數據持久化的幾種方式

文件

歸檔(NSKeyedArchiver)

屬性列表(NSUserDefaults)

數據庫(SQLite、CoreData、第三方類庫)

一、文件

應用程序包: 這里面存放的是應用程序的源文件,包括資源文件和可執行文件。NSString *path = [[NSBundle mainBundle] bundlePath];

/Documents:最常用的目錄,iTunes同步該應用時會同步此文件夾中的內容,適合存儲重要數據。NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;

Library/Caches: iTunes不會同步此文件夾,適合存儲體積大,不需要備份的非重要數據。NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;

Library/Preferences: iTunes同步該應用時會同步此文件夾中的內容,通常保存應用的設置信息。NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];

/tmp: iTunes不會同步此文件夾,系統可能在應用沒運行時就刪除該目錄下的文件,所以此目錄適合保存應用中的一些臨時文件,用完就刪除。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];

二、歸檔

歸檔(又名序列化),把對象轉為字節碼,以文件的形式存儲到磁盤上,程序運行過程中或者再次重新打開程序的時候,可以通過解歸檔(返序列化)還原這些對象。

歸檔的對象是Foundation框架中的對象

歸檔和解歸檔其中任意對象都需要歸檔和解歸檔整個文件

歸檔后的文件是加密的,所以歸檔文件的擴展名可以隨意取

在帶鍵的歸檔中,每個歸檔都有一個key值,解歸檔時key值要與歸檔時key值匹配

如果一個自定義的類A,作為另一個自定義類B的一個屬性存在;那么,如果要對B進行歸檔,那么,B要實現NSCoding協議。并且,A也要實現NSCoding協議

[NSKeyedArchiver archiveRootObject:obj toFile:appSettingPath];會調用對象的encodeWithCoder方法

- (void)encodeWithCoder:(NSCoder *)aCoder

{

? ? [aCoder encodeObject:_name forKey:kAddressCardName];


? ? [aCoder encodeObject:_emailObj forKey:kAddressCardEmail];


? ? [aCoder encodeInteger:_salary forKey:kAddressCardSalary];

}

[NSKeyedUnarchiver unarchiveObjectWithFile:appSettingPath];會調用對象的initWithCoder方法

- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder

{

? ? _name = [aDecoder decodeObjectForKey:kAddressCardName];


? ? _emailObj = [aDecoder decodeObjectForKey:kAddressCardEmail];


? ? _salary = [aDecoder decodeIntegerForKey:kAddressCardSalary];

? ? return self;

}

三、屬性列表

NSUserDefaults適合存儲輕量級的本地數據,一些簡單的數據(NSString類型的)例如密碼,網址等,NSUserDefaults肯定是首選,但是如果我們自定義了一個對象,對象保存的是一些信息,這時候就不能直接存儲到NSUserDefaults了。他的方便之處在于不用聲明太多的變量來存儲不同的數據,一個NSUserDefaults就可以搞定,他是應用程序域的,能讓我們進行更加方便的使用。

原理:NSUserDefaults類提供了與默認數據庫相交互的編程接口。其實它存儲在應用程序的一個plist文件里,路徑為沙盒Document目錄平級的/Library/Prefereces里。如果將默認數據庫比喻為SQL數據庫,那么NSUserDefaults就相當于SQL語句。

NSUserDefaults支持的數據類型有:NSNumber(NSInteger、float、double),NSString,NSDate,NSArray,NSDictionary,BOOL,NSData

user defaults數據庫中其實是由多個層級的域組成的,當你讀取一個鍵值的數據時,NSUserDefaults從上到下透過域的層級尋找正確的值,不同的域有不同的功能,有些域是可持久的,有些域則不行。

應用域(application domain)是最重要的域,它存儲著你app通過NSUserDefaults set…forKey添加的設置。

注冊域(registration domain)僅有較低的優先權,只有在應用域沒有找到值時才從注冊域去尋找。

全局域(global domain)則存儲著系統的設置

語言域(language-specific domains)則包括地區、日期等

參數域(argument domain)有最高優先權

注意:

偏好設置是專門用來保存應用程序的配置信息的,一般不要在偏好設置中保存其他數據。

如果沒有調用synchronize方法,系統會根據I/O情況不定時刻地保存到文件中。所以如果需要立即寫入文件的就必須調用synchronize方法。

偏好設置會將所有數據保存到同一個文件中。即preference目錄下的一個以此應用包名來命名的plist文件。

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

[defaults setObject:@"jack" forKey:@"firstName"];

[defaults setInteger:10 forKey:@"Age"];

[defaults synchronize];

四、SQLite

SQLite數據庫的幾個特點:

基于C語言開發的輕型數據庫

在iOS中需要使用C語言語法進行數據庫操作、訪問(無法使用ObjC直接訪問,因為libqlite3框架基于C語言編寫)

SQLite中采用的是動態數據類型,即使創建時定義了一種類型,在實際操作時也可以存儲其他類型,但是推薦建庫時使用合適的類型(特別是應用需要考慮跨平臺的情況時)

建立連接后通常不需要關閉連接(盡管可以手動關閉)

在iOS中操作SQLite數據庫可以分為以下幾個步驟:

打開數據庫,利用sqlite3_open()打開數據庫會指定一個數據庫文件保存路徑,如果文件存在則直接打開,否則創建并打開。打開數據庫會得到一個sqlite3類型的對象,后面需要借助這個對象進行其他操作。

執行SQL語句,執行SQL語句又包括有返回值的語句和無返回值語句。

對于無返回值的語句(如增加、刪除、修改等)直接通過sqlite3_exec()函數執行;

對于有返回值的語句則首先通過sqlite3_prepare_v2()進行sql語句評估(語法檢測),然后通過sqlite3_step()依次取出查詢結果的每一行數據,對于每行數據都可以通過對應的sqlite3column類型()方法獲得對應列的數據,如此反復循環直到遍歷完成。當然,最后需要釋放句柄。


打開數據庫

? ? if(sqlite3_open(filePath.UTF8String, &_database) == SQLITE_OK)

? ? {


? ? ? ? NSLog(@"數據庫打開成功");


? ? }


執行無返回結果的SQL

if(sqlite3_exec(_database, sql.UTF8String, NULL, NULL, &error) != SQLITE_OK)

? ? {


? ? ? ? NSLog(@"執行SQL語句過程中發生錯誤,錯誤信息:%s", error);

? ? }



執行有返回結果的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之后才出現的一個框架,它提供了對象-關系映射(ORM)的功能,即能夠將OC對象轉化成數據,保存在SQLite數據庫文件中,也能夠將保存在數據庫中的數據還原成OC對象。在此數據操作期間,我們不需要編寫任何SQL語句

簡介

Core Data是個框架(并不是數據庫哦),它使開發者可以把數據當做對象來操作,而不必在乎數據在磁盤中的存儲方式。對于iOS程序員來說,這很有用,因為我們已經可以通過代碼非常熟悉的操作對象了。由Core Data 所提供的數據對象叫做托管對象(Managed Object),而Core Data本身則位于你的應用程序和持久化存儲區(Persistent store)之間。為了把數據從托管對象映射到持久化存儲區中,Core Data 需要使用托管對象模型。所有的托管對象都必須位于托管對象上下文(Managed object context)里面,而托管對象上下文又位于高速的易失性存儲器里面,也就是位于RAM中。

為什么需要有托管對象上下文呢?原因之一就是在磁盤與RAM之間傳輸數據時會有開銷。磁盤讀寫速度比RAM慢的多,所以不應該頻繁地訪問它。有了托管對象上下文,就可以非常迅速地獲取到了。但它的缺點在于,開發者必須在托管對象上下文上面定期調用save方法,以將變更后的數據寫回磁盤。托管對象上下文的另一個功能是記錄開發者對托管對象所對的修改,以提供完整的撤銷和重做支持。

上邊我們對Core Data簡單的介紹了一下。接下來我們需要對CoreData中的重要的名詞做一解釋。

持久化存儲協調器NSPersistentStoreCoordinator

持久化存儲協調器(persistent store coordinator)里面包含一份持久化存儲區,而存儲區里面又含有數據表里面的若干行數據。設置持久化存儲協調器的時候,我們通常選用SQLite數據庫作為持久化存儲區。另外,也可以選用Binary、XML、或In-Memory等形式的持久化存儲區。但要注意的是,Binary和XML格式的存儲區是Atomic,也就是說,即便你只想修改少量的數據,在保存的時候也依然需要把整個文件都寫入磁盤。

同一個持久化存儲協調器可以有多個持久化存儲區。把CoreData與iCloud相集成的時候,就可能會出現這樣的情況。我們可以把不屬于iCloud的數據放在一個存儲區里面,而把屬于iCloud的數據放在另外一個存儲區里面,這樣既能節省網絡寬帶,又能節省iCloud存儲空間。

即便你有兩個持久化存儲區,也不意味著必須使用兩種對象圖。CoreData的模型配置允許開發者使用多個獨立的存儲區,但卻采用同一套對象圖。在設置CoreData的模型配置選項時,可以指明對象圖里面的某一部分屬于哪一個持久化存儲區。

要想創建持久化存儲區,需生成NSPersistentStore;要想創建持久化存儲協調器,需生成NSPersistentStoreCoordinator類的實例。

托管對象模型NSManagedObjectModel

托管對象模型它位于持久化存儲協調器和托管對象上下文之間。顧名思議,托管對象模型是描述數據結構的模型或者圖示,而托管對象正是以它為基礎產生出來的。可以用Xcode來配置實體(Entity)及實體之間的關系。實體本身并不包含數據,它們只是規定了基于該實體的托管對象具有何種特性。實體也有屬性,屬性的數據類型可以是整數,字符串,或者日期等。

要想創建托管對象模型,需要生成NSManagedObjectModel類的實例

托管對象上下文NSManagedObjectContext

托管對象上下文中可包含多個托管對象。托管對象上下文負責管理其中對象的生命期,并且負責提供許多強大的功能。

托管對象上下文也可以不止有一個,有時我們需要在后臺處理任務(比方說把數據保存到磁盤或者導入數據),這種情況下可以采用多個上下文。加入在前臺上下文上面調用Save,那么用戶界面就可能會有卡頓現象,尤其當數據變化較大的時候更是如此。要想避免這個問題,有個簡單的辦法就是只在用戶按下手機的Home鍵時才去調用Save,這時應用程序會轉入到后臺。還有個稍微復雜的但卻很靈活的辦法,就是采取兩個托管對象上下文。請記住,托管對象上下文是存放在高速內存里面的。你可以配置其中一個上下文,那么就可以將后臺上下文中的數據異步存入磁盤。這種分段式的做法可以確保磁盤寫入操作不會影響用戶界面的流暢度。

要想創建托管對象上下文,需要生成NSManagedObjectContext類的實例

coreData簡單創建流程

模型文件操作

創建模型文件,后綴名為.xcdatamodeld。創建模型文件之后,可以在其內部進行添加實體等操作(用于表示數據庫文件的數據結構)

添加實體(表示數據庫文件中的表結構),添加實體后需要通過實體,來創建托管對象類文件。

添加屬性并設置類型,可以在屬性的右側面板中設置默認值等選項。(每種數據類型設置選項是不同的)

創建獲取請求模板、設置配置模板等。

根據指定實體,創建托管對象類文件(基于NSManagedObject的類文件)

實例化上下文對象

創建托管對象上下文(NSManagedObjectContext)

創建托管對象模型(NSManagedObjectModel)

根據托管對象模型,創建持久化存儲協調器(NSPersistentStoreCoordinator)

關聯并創建本地數據庫文件,并返回持久化存儲對象(NSPersistentStore)

將持久化存儲協調器賦值給托管對象上下文,完成基本創建。

a15b4afegw1f9lymk9zhyj20yg100412.png

// 從應用程序包中加載模型文件

? NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];


? // 傳入模型對象,初始化持久化存儲協調器

? NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];


? // 構建SQLite數據庫文件的路徑

? 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:@"添加數據庫錯誤" format:@"%@", [error localizedDescription]];

? }

? // 創建托管對象上下文

? 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"];


? // 利用上下文對象,將數據同步到持久化存儲庫

? NSError *errorSave = nil;


? BOOL sucess = [context save:&errorSave];

查詢數據

NSFetchRequest

在執行fetch操作前,可以給NSFetchRequest設置一些參數,這些參數包括謂詞、排序等條件,下面是一些基礎的設置。

設置查找哪個實體,從數據庫的角度來看就是查找哪張表,通過fetchRequestWithEntityName:或初始化方法來指定表名。

通過NSPredicate類型的屬性,可以設置查找條件,這個屬性在開發中用得最多。NSPredicate可以包括固定格式的條件以及正則表達式。

通過sortDescriptors屬性,可以設置獲取結果數組的排序方式,這個屬性是一個數組類型,也就是可以設置多種排序條件。(但是注意條件不要沖突)

通過fetchOffset屬性設置從查詢結果的第幾個開始獲取,通過fetchLimit屬性設置每次獲取多少個。主要用于分頁查詢,后面會講。

MOC執行fetch操作后,獲取的結果是以數組的形式存儲的,數組中存儲的就是托管對象。NSFetchRequest提供了參數resultType,參數類型是一個枚舉類型。通過這個參數,可以設置執行fetch操作后返回的數據類型。

NSManagedObjectResultType: 返回值是NSManagedObject的子類,也就是托管對象,這是默認選項

NSManagedObjectIDResultType: 返回NSManagedObjectID類型的對象,也就是NSManagedObject的ID,對內存占用比較小。MOC可以通過NSManagedObjectID對象獲取對應的托管對象,并且可以通過緩存NSManagedObjectID參數來節省內存消耗

NSDictionaryResultType: 返回字典類型對象

NSCountResultType: 返回請求結果的count值,這個操作是發生在數據庫層級的,并不需要將數據加載到內存中

NSPredicate

在iOS開發過程中,很多需求都需要用到過濾條件。例如過濾一個集合對象中存儲的對象,可以通過Foundation框架下的NSPredicate類來執行這個操作。

CoreData中可以通過設置NSFetchRequest類的predicate屬性,來設置一個NSPredicate類型的謂詞對象當做過濾條件。通過設置這個過濾條件,可以只獲取符合過濾條件的托管對象,不會將所有托管對象都加載到內存中。這樣是非常節省內存和加快查找速度的,設計一個好的NSPredicate可以優化CoreData搜索性能。


// 從數據庫查詢數據

? ? 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;


? ? // 執行請求

? ? NSError *errorFetch = nil;


? ? NSArray *objs = [context executeFetchRequest:request error:&errorFetch];


? ? if(errorFetch)

? ? {

? ? ? ? [NSException raise:@"查詢錯誤" format:@"%@", [errorFetch localizedDescription]];

? ? }

總結

NSPersistentStoreCoordinator有四種可選的持久化存儲方案,用得最多的是SQLite的方式。其中Binary和XML這兩種方式,在進行數據操作時,需要將整個文件加載到內存中,這樣對內存的消耗是很大的。

NSSQLiteStoreType : SQLite數據庫

NSXMLStoreType : XML文件

NSBinaryStoreType : 二進制文件

NSInMemoryStoreType : 直接存儲在內存中

在coredata中所有的托管對象被創建出來后,都是關聯著context對象的,所以在對象進行任何操作后,都會被記錄在context中,在最后調用context的save方法后,context會將操作交給coordinator去處理,coordinator將會將這個存儲任務指派給NSPersistentStore對象。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容