iOS開發中的4種數據持久化方式【一、屬性列表與歸檔解檔】

iOS中~~的永久存儲,也就是在關機重新啟動設備,或者關閉應用時,不會丟失數據。在實際開發應用時,往往需要持久存儲數據的,這樣用戶才能在對應用進行操作后,再次啟動能看到自己更改的結果與痕跡。iOS開發中,我們需要數據持久化這一種技術,也需要不斷在實際開發的工作與學習中完善數據持久化這一開發技術。
【本次開發環境: Xcode:7.2 iOS Simulator:iphone6 By:啊左】
(本節2個項目demo的下載:屬性列表Demo對象的歸檔解檔Demo

本文將介紹4種數據持久化的方法:
1、屬性列表
2、對象的歸檔、解檔
3、數據庫 SQLite3 的運用
4、Core Data 的運用
當然,iOS開發中,持久化數據的方法不局限于以上這4種方法,還可以使用啊左的博客:ios開發--應用設置及用戶默認設置【1、bundle的運用】這篇博客介紹的應用設置的存儲方法;
也可以使用傳統的C語言I/O調用(比如:fopen() )的讀取與寫入數據,可以使用Cocoa的底層文件管理工具,只不過這兩種方法都需要開發者寫入很多代碼,本文不作介紹,如果需要的話,讀者可以上網找一下。

在介紹4種持久化存儲方式前,我們需要先介紹3個有關的文件夾,以及沙盒機制:
Documents:應用會將數據存儲在這個文件夾里,但是基于NSUserDefaults 的首選項設置除外;
Library:基于NSUserDefaults的首選項設置存儲在 Library/Preferences 文件夾中,且Library下面有Preferences和Caches目錄;
tmp:供應用存儲臨時文件,當iOS設備進行同步操作時,iTunes并不會備份這個文件夾的文件,但是在不需要這些文件的時候,應用需要刪除tmp中的這些文件,以免占用文件系統空間;

什么是沙盒機制?
我們手中的iphone/ipad設備上包含著閃存(flash memory),它的功能和一個硬盤功能等價。當設備斷電后數據依然能夠被保存下來,應用程序可以把數據文件保存到閃存上,并且讀取它們。但是,需要注意的是,我們所開發的應用程序是無法訪問整個閃存的,因為閃存上面會專門有一部分給我們,這一部分就是屬于我們開發的整個應用程序的沙盒(sandbox)了。iOS系統下,每個應用都只能看到自己的沙盒,這就防止對其他應用程序的數據文件進行讀寫活動。就像我們的應用程序也能夠看見一些系統擁有的高級別目錄,但是卻無法進行任何的寫入操作;

那么,如何獲取屬于自己的目錄?
1、獲取Documents目錄
由于iOS中應用的數據存儲是沙盒機制,因此讀取和寫入文件,我們需要調用C函數 “NSSearchPathForDirectoriesInDomains()”來查找各種目錄,(這個C函數可以基于Mac OS X平臺的Cocoa共享)
如檢索Documents目錄路徑的代碼:

(好吧,在這篇簡書中終于學會上代碼框了.555...心疼啊左。)

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *pathDirectory = [paths objectAtIndex:0];
//或者NSString *pathDirectory = [paths lastObject];

第一個常量NSDocumentDirectory表示正在查找沙盒Document目錄的路徑(如果參數為NSCachesDirectory則表示沙盒Cache目錄),第二個常量NSUserDomainMask表明我們希望將搜索限制在應用的沙盒內;(在Mac OS X中,此常量表示我們希望該函數查看用戶的主目錄,因此才會有這個命名;)
返回的是一個數據paths,為什么位于索引0就是我們需要的Documents目錄?因為每一個應用只有一個Documents目錄,因此只有一個目錄符合這個條件;接下來,我們可以為剛才檢索到的目錄pathDirectory的結尾加一個字符串來創建一個文件名,如下:

NSString *filename = [pathDirectory stringByAppendingPathComponent:@"data.txt"];
//注意是stringByAppendingPathComponent,不要拼錯。

這個時候我們得到的filename字符串就可以進行創建、讀取、寫入文件了。

2、獲取tmp目錄:
可以用NSTemporaryDirectory()的Foundation函數返回一個字符串,該字符串包含到應用臨時目錄的完整路徑。 同上,在結尾附上文件名就可以創建指向該目錄下的文件路徑了。

NSString *tmpPath = NSTemporaryDirectory();
NSString *temFile = [tmpPath stringByAppendingPathComponent:@"tempFile.txt"];

下面介紹數據持久化方法的具體實現:

一、屬性列表
在【1、bundle的運用】中,我們使用了屬性列表來指定應用的默認設置與相應的數據存儲,并且方便使用Xcode或者Property List Editor應用手動編輯它們,只要字典或者數據包含特定可序列化對象,就可以NSDictionary和NSArray實例寫入屬性列表或者從屬性列表創建相應的對象;

什么是序列化對象?
序列化對象(Serialized objects),是指可以被轉換為字節流以便于存儲到文件中或者通過網絡進行傳輸的對象;
雖然說任何對象都可以被序列化,但是只有某些特定的對象才能放置到某個集合類(例如:NSArray、 NSMutableArray、NSDictionary、 NSData等)中,并使用該集合類的方法在屬性列表存儲中使用,其他的對象也可以使用歸檔的方法進行存儲(在對象的歸檔、解檔我們會進行詳細介紹)。

那我們開始構建第一個使用屬性列表存儲數據的簡單應用:
具體的功能效果如【圖1】,可以讓用戶在4個文本框中輸入數據,應用退出時會把這些字段保存到屬性列表中,并在下次啟動時重現加載恢復上次的數據;



【圖1 效果圖】

在Xcode中,使用Single View Application模板創建一個新項目,命名為persistence1。
在“Main.storyboard”中拖入4個標簽、4個文本框控件,拖動并對齊標簽與文本框,并依次修改標簽文本如【圖1】,“ViewController.h”中添加一個裝載4個文本框的數組“lineFields”:

#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (strong,nonatomic)
IBOutletCollection(UITextField) NSArray *lineFields;@end

打開輔助編輯器,通過control鍵將4個文本框連接到 lineFields 這個數組,確保連接順序為從頂部到底部!
在項目導航面板中,點擊"ViewController.m" ,將以下代碼添加到@implementation與 @end 的中間,這個方法在后面會一直調用:

//獲取屬性列表路徑中數據文件的完整路徑 dataFilepath
//需要加載和保存數據的代碼都可以調用該方法.
-(NSString *)dataFilepath{

NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
NSString *pathDirectory = [paths objectAtIndex:0]; 
return [pathDirectory stringByAppendingPathComponent:@"data.txt"];

}

接下來,在viewDidload中添加代碼,并添加相應的響應器方法:

- (void)viewDidLoad { 
[super viewDidLoad];

NSString *filePath = [self dataFilepath]; 

//判斷是否存在屬性列表文件 
if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { 
//存在,則把數據賦值給文本框 
NSArray *ar= [[NSArray alloc]initWithContentsOfFile:filePath]; 

for(int i =0;i<4;i++) { 
UITextField *textField = self.lineFields[i]; 
textField.text = ar[i]; 
} 
} 

//如果應用進入后臺: 
UIApplication *app = [UIApplication sharedApplication]; 
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(applicationWillResignActiveNotification:) name:UIApplicationWillResignActiveNotification object:app];
}
//應用進入后臺時執行:
-(void)applicationWillResignActiveNotification:(NSNotification *)notification{ 
NSString *pathFile = [self dataFilepath]; 
//我們不是用迭代數組的形式,而是用了便捷的方法,使用NSarrry類中的valueForKey方法,把lineFields中包含@“text”值的數組賦值給array. 
NSArray *array = [self.lineFields valueForKey:@"text"]; 
//把字符串數組寫入文件。  
[array writeToFile:pathFile atomically:YES]; 
}

這段代碼的意思是,首先檢查完整路徑下的數據文件是否存在,不存在的話就不加載了;
若存在,則把數組中的對象復制到4個文本框中,根據剛才我們創建數組“lineFields”的時候,與文本框的連接順序,就可以把數據賦值給文本框了。
然后在應用終止或者進入后臺之前進行數據的保存處理,所以我們使用通知中心,訂閱了名為 “UIApplicationWillResignActiveNotification” 的通知,并在后面實現了“applicationWillResignActiveNotification”這個方法。
當用戶按下手機的“Home鍵”,或者其他事件發生(比如來電)導致應用進入后臺的情況,便調用此方法,把字符串數組寫入我們創建的屬性列表文件里面。

好了,我們已經完成了GUI界面的基礎設計以及代碼的編程了,接下來,按下“command+R”運行它;
如果沒有其他問題的話,我們可以分別鍵入4個文本框,然后點擊Home鍵(也就是command+shift+H)、雙擊Home鍵(按住command+shift,雙擊H),或者在Xcode中終止應用退出模擬器(相當于手機重啟),以驗證數據在應用得到永久保存了。
總結:屬性列表的序列化很實用,也相對比較簡單,但是也會有點限制,就是只能將一小部分對象保存在屬性列表中,接下來我們介紹下強大的歸檔解檔對象的數據儲存方法;

二、對模型對象進行歸檔、解檔
就像我們前面屬性列表的介紹,歸檔(archiving)也是指另一種形式的序列化。但強大的一點是,它是任何對象都可以實現的更常規的儲存數據類型;
在進行歸檔、解檔的開發中,我們需要一起實現的,還有NSCoding和NSCopying協議,需要說明的是,標量(如int或float)以及大多數Foundation和Cocoa Touch類都遵循NSCoding協議(有例外,如UIImage不遵循),因此大多數類,還是比較容易實現歸檔操作的;
1、遵循NSCoding協議、NSCopying協議
NSCoding協議聲明了2個方法:一個是將對象編碼到歸檔中,另一個是對歸檔的解碼來恢復我們之前歸檔的對象,使用方法與NSUserDefaults相似也可以用KVC對對象和原生數據類型(如int和float)進行編碼和解碼。
NSCopying協議用于允許復制對象,使得使用數據模型對象時具備較大的靈活性;
2、歸檔、解檔
歸檔:創建一個NSKeyedArchiver實例,用于將對象歸檔到一個NSMutableData實例中,此時NSMutableData包含編碼的數據,再使用鍵/碼對需要的對象進行歸檔,最后告知完成,寫入文件系統;
解檔:也與歸檔對象步驟類似,創建一個NSData實例用于裝載數據,并創建一個NSKeyedUnarchiver實例,對數據解碼,然后使用先前用的鍵進行讀取對象,最后告知程序解檔完成;

這樣說有點干,囧~ 還是上代碼吧:
a."linePesist"類的創建
在Xcode中,使用Single View Application模板創建一個新項目,命名為persistence2,沒錯,還是跟屬性列表一樣的應用模板。
但是需要創建一個新文件,按command+N,或者從File菜單中依次選擇New->New File。出現新建文件向導后,選擇Cocoa Touch,然后選擇Objective-C class,單擊Next,將類命名為“linePesist”,并在“Subclass of”一欄中選擇NSObject,單擊Next,再單擊Create。該類做為我們的數據模型,并且將用于存儲屬性列表應用的字典中的數據。
單擊“linePesist.h”,修改代碼如下:

#import <Foundation/Foundation.h>
//遵循NSCoding、NSCopying協議
@interface linePesist : NSObject<NSCoding,NSCopying>
@property (nonatomic,copy)NSArray *array;
@end

這是一個擁有數組類型的簡單數據模型,數組可以用于我們放置文本框的數據字段。
接下來,我們進行“linePesist.m”的編輯:

#import "linePesist.h"
#define CodeStr @"CodeStr" 
//用于歸檔解檔的時候用的鍵名
@implementation linePesist
/* 通過遵循NSCoding和NSCoping中的方法,創建可歸檔的數據對象。*/

#pragma mark -- Coding
//編碼
-(void)encodeWithCoder:(NSCoder *)aCoder{ 
[aCoder encodeObject:self.array forKey:CodeStr];
}

//解碼
-(id)initWithCoder:(NSCoder *)aDecoder{ 
self = [super init]; 
if(self) { self.array = [aDecoder decodeObjectForKey:CodeStr]; 
} 
return self;
}

#pragma mark -- Coping
-(id)copyWithZone:(NSZone *)zone{ 
linePesist *copy = [[[self class]allocWithZone:zone] init];
 NSMutableArray *muAr = [[NSMutableArray alloc]init]; 
for(id line in self.array) { 
[muAr addObject:[line copyWithZone:zone]]; 
} 
copy.array = muAr; return copy;
}
@end

用預定義的“CodeStr”做為編碼解碼的鍵,存儲4個文本框的字符串,然后用同樣的“CodeStr”鍵進行解碼,將4個字符串復制到copyWithZone創建的linePesist對象中;
b.“ViewController”類實現
創建可歸檔的數據對象之后,我們便可以使用此來進行持久化的存儲。點擊“ViewController.m”編輯界面,并進行以下除劃掉的部分的代碼編輯。

#import "ViewController.h"
#import "linePesist.h"
//導入數據模型類
#define CodeString  @"CodeString"
@implementation ViewController

-(NSString *)dataFile{ 
NSArray *ar = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES); 
NSString *fielpath = [ar objectAtIndex:0];
return [fielpath stringByAppendingPathComponent:@"data.archive"]; 
//改修后綴名,以免與屬性列表創建的文件重復,而加載成舊的的文件。 不用查字典了。。archive表歸檔
}
- (void)viewDidLoad { 
[super viewDidLoad]; 

NSString *filepath = [self dataFile]; 
NSLog(@"%@",filepath); 

if([[NSFileManager defaultManager]fileExistsAtPath:filepath]) { 
//創建2個實例 
NSData *data = [[NSData alloc]initWithContentsOfFile:filepath];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data]; 
//把已歸檔的對象讀取。賦值給linepesist 
linePesist *linepesist =[unarchiver decodeObjectForKey:CodeString]; 
[unarchiver finishDecoding]; 
//完成解檔 
for(int i = 0;i<4;i++) { 
//把解檔的數據分別賦值給文本框 
UITextField *textField = self.fourLines[i]; 
textField.text = linepesist.array[i]; 
//記得是.text
 } } 
UIApplication *app = [UIApplication sharedApplication]; 
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(applicationWillResignActiveNotification:) name:UIApplicationWillResignActiveNotification object:app];
}

// 應用回到后臺,數據歸檔、寫入文件中
—(void)applicationWillResignActiveNotification:(NSNotification*)notfication{ 
NSString *pathField = [self dataFile]; 
linePesist *linepesit = [[linePesist alloc]init]; 
linepesit.array = [self.fourLines valueForKey:@"text"]; 
//創建2個實例 
NSMutableData *data = [[NSMutableData alloc]init]; 
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data]; 
//使用鍵/值編碼對希望包含在歸檔中的對象進行歸檔。 
[archiver encodeObject:linepesit forKey:CodeString]; 
[archiver finishEncoding]; 
//與屬性列表一樣,需要在最后寫入文件,因為屬性列表與歸檔都是一種序列化,最后仍需要寫入文件。 
[data writeToFile:pathField atomically:YES];
}
@end

除了存儲數據的方式不一樣,GUI界面與上一個版本的一致。運行這個版本的persistence應用。效果應該也與我們運行屬性列表時的一樣。

好了,屬性列表、歸檔解檔對象的存儲方法我們介紹到這里,讀者可以對比下有什么不同,呃...最明顯的應該是歸檔的代碼量多些,但是相應的,歸檔會具有非常好的伸縮性,至少從代碼上面看,是這樣的。
在下一節,我們將介紹另外2種方法:【數據庫 SQLite3 的運用、[Core Data 的運用]】


(轉載請標明原文出處,謝謝支持 ~ - ~)
? by:啊左~

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

推薦閱讀更多精彩內容