iOS中的永久存儲,也就是在關機從新啓動設備,或者關閉應用時,不會丟失數據。在實際開發應用時,每每須要持久存儲數據的,這樣用戶才能在對應用進行操做後,再次啓動能看到本身更改的結果與痕跡。iOS開發中,咱們須要數據持久化這一種技術,也須要不斷在實際開發的工做與學習中完善數據持久化這一開發技術。html
【本次開發環境: Xcode:7.2 iOS Simulator:iphone6S plus By:啊左】 ios
(本節2個項目demo的下載:屬性列表Demo、對象的歸檔解檔Demo)git
本文將介紹4種數據持久化的方法:github
一、屬性列表數據庫
二、對象的歸檔、解檔編程
三、數據庫 SQLite3 的運用數組
四、Core Data 的運用網絡
固然,iOS開發中,持久化數據的方法不侷限於以上這4種方法,還可使用啊左的博客:ios開發--應用設置及用戶默認設置【一、bundle的運用】這篇博客介紹的應用設置的存儲方法;app
也可使用傳統的C語言I/O調用(好比:fopen() )的讀取與寫入數據,可使用Cocoa的底層文件管理工具,只不過這兩種方法都須要開發者寫入不少代碼,本文不做介紹,若是須要的話,讀者能夠上網找一下。iphone
在介紹4種持久化存儲方式前,咱們須要先介紹3個有關的文件夾,以及沙盒機制:
什麼是沙盒機制?
咱們手中的iphone/ipad設備上包含着閃存(flash memory),它的功能和一個硬盤功能等價。當設備斷電後數據依然可以被保存下來,應用程序能夠把數據文件保存到山村上,而且讀取它們。可是,須要注意的是,咱們所開發的應用程序是沒法訪問整個閃存的,由於閃存上面會專門有一部分給咱們,這一部分就是屬於咱們開發的整個應用程序的沙盒(sandbox)了。iOS系統下,每一個應用都只能看到本身的沙盒,這就防止對其餘應用程序的數據文件進行讀寫活動。就像咱們的應用程序也可以看見一些系統擁有的高級別目錄,可是卻沒法進行任何的寫入操做;
那麼,如何獲取屬於本身的目錄?
一、獲取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表示正在查找沙盒Document目錄的路徑(若是參數爲NSCachesDirectory則表示沙盒Cache目錄),第二個常量NSUserDomainMask代表咱們但願將搜索限制在應用的沙盒內;(在Mac OS X中,此常量表示咱們但願該函數查看用戶的主目錄,所以纔會有這個命名;)
返回的是一個數據paths,爲何位於索引0就是咱們須要的Documents目錄?由於每個應用只有一個Documents目錄,所以只有一個目錄符合這個條件;
接下來,咱們能夠爲剛纔檢索到的目錄pathDirectory的結尾加一個字符串來建立一個文件名,以下:
NSString *filename = [pathDirectory stringByAppendingPathComponent:@"data.txt"];
//注意是stringByAppendingPathComponent,不要拼錯。
這個時候咱們獲得的filename字符串就能夠進行建立、讀取、寫入文件了。
二、獲取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中終止應用退出模擬器(至關於手機重啓),以驗證數據在應用獲得永久保存了。
總結:屬性列表的序列化很實用,也相對比較簡單,可是也會有點限制,就是隻能將一小部分對象保存在屬性列表中,接下來咱們介紹下強大的歸檔解檔對象的數據儲存方法;
2、對模型對象進行歸檔、解檔
就像咱們前面屬性列表的介紹,歸檔(archiving)也是指另外一種形式的序列化。但強大的一點是,它是任何對象均可以實現的更常規的儲存數據類型;
在進行歸檔、解檔的開發中,咱們須要一塊兒實現的,還有NSCoding和NSCopying協議,須要說明的是,標量(如int或float)以及大多數Foundation和Cocoa Touch類都遵循NSCoding協議(有例外,如UIImage不遵循),所以大多數類,仍是比較容易實現歸檔操做的;
一、遵循NSCoding協議、NSCopying協議
NSCoding協議聲明瞭2個方法:一個是將對象編碼到歸檔中,另外一個是對歸檔的解碼來恢復咱們以前歸檔的對象,使用方法與NSUserDefaults類似也能夠用KVC對對象和原生數據類型(如int和float)進行編碼和解碼。
NSCopying協議用於容許複製對象,使得使用數據模型對象時具有較大的靈活性;
二、歸檔、解檔
歸檔:建立一個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 [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 的運用;