1. 程式人生 > >ios開發中的4種資料持久化方式

ios開發中的4種資料持久化方式

iOS中的永久儲存,也就是在關機重新啟動裝置,或者關閉應用時,不會丟失資料。在實際開發應用時,往往需要持久儲存資料的,這樣使用者才能在對應用進行操作後,再次啟動能看到自己更改的結果與痕跡。ios開發中,我們需要資料持久化這一種技術,也需要不斷在實際開發的工作與學習中完善資料持久化這一開發技術。

本文將介紹4種資料持久化的方法:

1、屬性列表

2、物件的歸檔、解檔

3、資料庫 SQLite3 的運用

4、Core Data 的運用

也可以使用 傳統的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鍵(也就是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 [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應用。效果應該也與我們執行屬性列表時的一樣。

好了,屬性列表、歸檔解檔物件的儲存方法我們介紹到這裡,讀者可以對比下有什麼不同,呃...最明顯的應該是歸檔的程式碼量多些,但是相應的,歸檔會具有非常好的伸縮性,至少從程式碼上面看,是這樣的。