iOS開發中的4種數據持久化方式(1) :屬性列表與歸檔解檔
iOS中的永久存儲,也就是在關機重新啟動設備,或者關閉應用時,不會丟失數據。在實際開發應用時,往往需要持久存儲數據的,這樣用戶才能在對應用進行操作後,再次啟動能看到自己更改的結果與痕跡。
iOS開發中,我們需要數據持久化這一種技術,也需要不斷在實際開發的工作與學習中完善數據持久化這一開發技術。
【本次開發環境: Xcode:7.2 iOS Simulator:iphone6】
(本節2個項目demo的下載:屬性列表Demo、對象的歸檔解檔Demo)
本文將介紹4種數據持久化的方法:
1、屬性列表
2、對象的歸檔、解檔
3、數據庫 SQLite3 的運用
4、Core Data 的運用
當然,iOS開發中,持久化數據的方法不局限於以上這4種方法,還可以使用啊左的博客:應用設置及用戶默認設置【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目錄路徑的代碼:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *pathDirectory = [paths objectAtIndex:0]; //或者NSString *pathDirectory = [paths lastObject];
第一個常量NSDocumentDirectory表示我們正在查找目錄的路徑,第二個常量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 @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個文本框中,根據剛才我們創建數組“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 //遵循NSCoding、NSCopying協議 @interface linePesist : NSObject @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
除了存儲數據的方式不一樣,GUI界面與上一個版本的一致。運行這個版本的persistence應用。效果應該也與我們運行屬性列表時的一樣。
好了,屬性列表、歸檔解檔對象的存儲方法我們介紹到這裡,讀者可以對比下有什麼不同,呃…最明顯的應該是歸檔的代碼量多些,但是相應的,歸檔會具有非常好的伸縮性,至少從代碼上面看,是這樣的。
在下一節,我們將介紹另外2種方法:【數據庫 SQLite3 的運用、[Core Data 的運用]】
本文作者: 伯樂在線 - 啊左~