ios開發中的4種資料持久化方式【一、屬性列表與歸檔解檔】,ios4種
iOS中的永久儲存,也就是在關機重新啟動裝置,或者關閉應用時,不會遺失資料。在實際開發應用時,往往需要持久儲存資料的,這樣使用者才能在對應用進行操作後,再次啟動能看到自己更改的結果與痕迹。ios開發中,我們需要資料持久化這一種技術,也需要不斷在實際開發的工作與學習中完善資料持久化這一開發技術。
本文將介紹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 檔案夾中;
- tmp:供應用儲存臨時檔案,當iOS裝置進行同步操作時,iTunes並不會備份這個檔案夾的檔案,但是在不需要這些檔案的時候,應用需要刪除tmp中的這些檔案,以免佔用檔案系統空間;
如何擷取?
1、擷取Documents目錄
由於iOS中應用的資料存放區是沙箱機制,因此讀取和寫入檔案,我們需要調用C函數 “NSSearchPathForDirectoriesInDomains()”來尋找各種目錄,(這個C函數可以基於Mac OS X平台的Cocoa共用)
如檢索Documents目錄路徑的代碼:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);NSString *pathDirectory = [paths objectAtIndex:0];
第一個常量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 <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鍵、雙擊Home鍵,或者在Xcode中終止應用退出模擬器(相當於手機重啟),以驗證資料在應用得到永久儲存了。
總結:屬性列表的序列化很實用,也相對比較簡單,但是也會有點限制,就是只能將一小部分對象儲存在屬性列表中,接下來我們介紹下強大的歸檔解檔對象的資料儲存方法;
二、對模型對象進行歸檔、解檔
就像我們前面屬性列表的介紹,歸檔(archiving)也是指另一種形式的序列化。但強大的一點是,它是任何對象都可以實現的更常規的儲存資料類型;
在進行歸檔、解檔的開發中,我們需要一起實現的,還有NSCoding和NSCopying協議,需要說明的是,標量(如int或float)以及大多數Foundation和Cocoa Touch類都遵循NSCoding協議(有例外,如UIImage不遵循),因此大多數類,還是比較容易實現歸檔操作的;
1、遵循NSCoding協議、NSCopying協議
NSCoding協議聲明了2個方法:一個是將對象編碼到歸檔中,另一個是對歸檔的解碼來恢複我們之前歸檔的對象,使用方法與NSUserDefaults相似也可以用KVC對對象和原生資料類型(如int和float進行編碼和解碼)
NSCopying協議用於允許複製對象,使得使用資料模型對象時具備較大的靈活性;
2、歸檔、解檔
歸檔:建立一個NSKeyedArchiver執行個體,用於將對象歸檔到一個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 [pathDirectory stringByAppendingPathComponent:@"data.txt"]; return [fielpath stringByAppendingPathComponent:@"data.archive"]; //改修尾碼名,以免與屬性列表建立的檔案重複,而載入成舊的的檔案。 不用查字典了。。archive表歸檔}- (void)viewDidLoad { [super viewDidLoad]; NSString *filepath = [self dataFile]; NSLog(@"%@",filepath); 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]; } //建立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{ NSArray *array = [self.lineFields valueForKey:@"text"]; [array writeToFile:pathFile atomically:YES]; 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 的運用;