導(dǎo)語
數(shù)據(jù)持久化是一種非易失性存儲(chǔ)技術(shù)空免,在重啟動(dòng)計(jì)算機(jī)或設(shè)備后也不會(huì)丟失數(shù)據(jù)顽染,是將內(nèi)存中的數(shù)據(jù)模型轉(zhuǎn)換為存儲(chǔ)模型草丧,以及將存儲(chǔ)模型轉(zhuǎn)換為內(nèi)存中的數(shù)據(jù)模型的統(tǒng)稱狸臣。數(shù)據(jù)模型可以是任何數(shù)據(jù)結(jié)構(gòu)或?qū)ο竽P?存儲(chǔ)模型可以是關(guān)系模型、XML昌执、二進(jìn)制流等烛亦。持久化技術(shù)主要用于MVC模型中的model層。目前iOS平臺(tái)上主要使用如下的四種技術(shù):
一.NSUserDefaults(關(guān)鍵詞:屬性列表懂拾、xml序列化)
什么是NSUserDefaults煤禽?
在介紹NSUserDefaults之前,我們有必要先了解屬性列表的概念:屬性列表是一種基于xml序列化的數(shù)據(jù)永久存儲(chǔ)文件岖赋,又稱plist文件凛篙,原理是將一些基本數(shù)據(jù)類型讀寫進(jìn)plist文件(注:plist文件是xml格式文件,因?yàn)槌S糜诖鎯?chǔ)配置信息踱蠢,所以又稱作plist格式文件)并以明文方式存儲(chǔ)于設(shè)備中撮抓。許多OC的基本數(shù)據(jù)類型(如NSArray、NSString等)本身提供了向plist文件讀寫的方法栗涂,但實(shí)際項(xiàng)目中我們用的更多的是NSUserDefaults知牌,NSUserDefaults是蘋果基于屬性列表所封裝的一個(gè)單例類,該類提供了基本數(shù)據(jù)類型的plist文件存儲(chǔ)方法斤程,因其使用方便角寸,代碼易懂,NSUserDefaults成為了最常用的數(shù)據(jù)持久化方式之一忿墅。
NSUserDefaults常用方法
//從 NSUserDefaults 中取出 key 值所對(duì)應(yīng)的 Value
id = [[NSUserDefaults standardUserDefaults] objectForKey:(NSString *)];
//將數(shù)據(jù)對(duì)象存儲(chǔ)到 NSUserDefaults 中
[[NSUserDefaults standardUserDefaults] setObject:(id)
forKey:(NSString *)];
//將數(shù)據(jù)對(duì)象從 NSUserDefaults 中移除
[[NSUserDefaults standardUserDefaults] removeObjectForKey(NSString *)];
//同步更新到Plist文件扁藕,當(dāng)修改了 NSUserDefaults 的數(shù)據(jù)后,必須進(jìn)行此步操作
[[NSUserDefaults standardUserDefaults] synchronize];
NSUserDefaults特點(diǎn)
- NSUserDefaults常用于存儲(chǔ)OC基本數(shù)據(jù)類型疚脐,不適合存儲(chǔ)自定義對(duì)象亿柑,NSUserDefaults支持的數(shù)據(jù)類型有:NSNumber(NSInteger、float棍弄、double)望薄,NSString疟游,NSDate,NSArray痕支,NSDictionary颁虐,BOOL.
- 自定義對(duì)象可以轉(zhuǎn)化成基本類型NSData后再使用NSUserDefaults進(jìn)行存儲(chǔ),但并不常用卧须。
- 當(dāng)plist文件存儲(chǔ)的數(shù)據(jù)發(fā)生改變(寫操作)時(shí)另绩,需要調(diào)用synchronize方法同步,否則數(shù)據(jù)無法同步保存花嘶。
- Key值應(yīng)具有唯一性笋籽,重名時(shí)將覆蓋先前的key值。
- 實(shí)際開發(fā)中椭员,NSUserDefaults常用于存儲(chǔ)配置信息干签,優(yōu)點(diǎn)是簡(jiǎn)便,缺點(diǎn)是所有數(shù)據(jù)都以明文存儲(chǔ)在plist文件中拆撼,容易被解讀導(dǎo)致安全性不高容劳。
二.對(duì)象歸檔(關(guān)鍵詞:序列化)
什么是對(duì)象歸檔?
和屬性列表一樣闸度,對(duì)象歸檔也是將對(duì)象寫入文件并保存在硬盤內(nèi)竭贩,所以本質(zhì)上是另一種形式的序列化(儲(chǔ)存模型不同)。雖說任何對(duì)象都可以被序列化莺禁,但只有某些特定的對(duì)象才能放置到某個(gè)集合類(例如:NSArray留量、NSMutableArray、NSDictionary哟冬、NSData等)中楼熄,并使用該集合類的方法在屬性列表存儲(chǔ)中讀寫,一旦將包含了自定義對(duì)象的數(shù)組寫入屬性列表浩峡,程序就會(huì)報(bào)錯(cuò)可岂。歸檔與屬性列表方式不同,屬性列表只有指定的一些對(duì)象才能進(jìn)行持久化且明文存儲(chǔ)翰灾,而歸檔是任何實(shí)現(xiàn)了NSCopying協(xié)議的對(duì)象都可以被持久化缕粹,且歸檔后的文件是加密的。對(duì)象歸檔涉及兩個(gè)類:NSKeyedArchiver和NSKeyedUnarchiver纸淮,這兩個(gè)類是NSCoder的子類平斩,分別用于歸檔以及解檔。下面將介紹如何對(duì)自定義對(duì)象進(jìn)行歸檔咽块。
對(duì)象歸檔示例
現(xiàn)在绘面,我們有一個(gè)自定義的Person類,該類有name,age揭璃,height三個(gè)屬性晚凿,其.h文件如下
//Person.h
#import<Foundation/foundation.h>
@interface Person:NSObject<NSCoding>
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)int age;
@property(nonatomic,assign)double height;
在.m文件中,我們要實(shí)現(xiàn)NSCoding中的兩個(gè)協(xié)議方法塘辅,這兩個(gè)方法分別在歸檔和解檔時(shí)會(huì)被調(diào)用,Person類的.m文件如下
//Person.m
#import"Person.h"
@implementation Person
/*
* 歸檔時(shí)調(diào)用該方法皆撩,該方法說明哪些數(shù)據(jù)需要儲(chǔ)存扣墩,怎樣儲(chǔ)存
*/
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:_name forKey:@"name"];
[encoder encodeInt:_age forKey:@"age"];
[encoder encodeDouble:_name forKey:@"height"];
}
/*
* 歸檔時(shí)調(diào)用該方法,該方法說明哪些數(shù)據(jù)需要解析扛吞,怎樣解析
*/
-(id)initWithCoder:(NSCoder *)decode
{
if (self = [super init]) {
_name = [decode decodeObjectForKey:@"name"];
_age = [decode decodeIntForKey:@"age"];
_height = [decode decodeDoubleForKey:@"height"];
}
return self;
}
@end
這個(gè)Person類就具有了歸檔與解檔能力呻惕,當(dāng)你需要對(duì)一個(gè)Person類的實(shí)力對(duì)象進(jìn)行儲(chǔ)存或者解析時(shí),在你自己的方法中只要鍵入如下代碼即可滥比,下面兩個(gè)方法對(duì)應(yīng)兩個(gè)按鈕的回調(diào)亚脆,點(diǎn)擊按鈕時(shí)分別執(zhí)行person對(duì)象的歸檔和解檔。
//寫操作
- (IBAction)Write {
Person *p = [[Person alloc]init];
p.name = @"jin";
p.age = 10;
p.height = 176.0;
//設(shè)置歸檔后文件路徑
NSString *path = @"/Users/macbookair/Desktop/person.data";
//歸檔
[NSKeyedArchiver archiveRootObject:p toFile:path];
}
//讀操作
- (IBAction)read {
//設(shè)置路徑
NSString *path = @"/Users/macbookair/Desktop/person.data";
//解檔
Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
NSLog(@"%@--%d---%f",p.name ,p.age ,p.height);
}
對(duì)象歸檔特點(diǎn)
- 可以將自定義對(duì)象寫入文件或從文件中讀出盲泛。
- 由于歸檔時(shí)進(jìn)行了加密處理濒持,因此安全性高于屬性列表。
三.CoreData(關(guān)鍵詞:集成化)
什么是CoreData寺滚?
當(dāng)你的應(yīng)用程序需要在本地存儲(chǔ)大量的關(guān)系型數(shù)據(jù)模型時(shí)柑营,顯然上述方法并不適合,因?yàn)椴徽搶?duì)象歸檔還是數(shù)據(jù)列表村视,一旦數(shù)據(jù)模型之間存在依賴關(guān)系官套,問題就將變得復(fù)雜。而此時(shí)iPhone自帶的輕量級(jí)數(shù)據(jù)庫Sqlite便成為我們的首選蚁孔,如果你熟悉數(shù)據(jù)庫奶赔,那么恭喜,CoreData也將不再神秘杠氢,你可以理解為它是蘋果對(duì)Sqlite封裝的一個(gè)框架站刑,你可以在Xcode中進(jìn)行Sqlite數(shù)據(jù)庫的可視化操作。倘若你對(duì)Sqlite感到陌生鼻百,那么本文最后也將對(duì)Sqlite進(jìn)行簡(jiǎn)單的講解笛钝。
如何使用CoreData?
1.新建自帶CoreData文件的工程項(xiàng)目,當(dāng)你創(chuàng)建成功后愕宋,左邊文件列表里將看到xxx.xcdatamodeld玻靡。并且在AppDelegate類中會(huì)自動(dòng)生成一系列相關(guān)屬性和方法用于管理CoreData。當(dāng)然你也可以為已有項(xiàng)目添加CoreData文件中贝,不過AppDelegate類則需要你重新構(gòu)造相關(guān)方法囤捻。
2.點(diǎn)擊xxx.xcdatamodeld文件,你會(huì)看到一個(gè)CoreData文件相當(dāng)于一個(gè)數(shù)據(jù)庫邻寿,在這里你可以進(jìn)行建表蝎土、添加屬性视哑、連接表與表之間的依賴關(guān)系等可視化操作。但是不能進(jìn)行數(shù)據(jù)的增刪改查誊涯,增刪改查需要代碼中自行實(shí)現(xiàn)挡毅。這里我們創(chuàng)建了名為User(用戶)和Department(部門)兩張表
3.設(shè)置完表的屬性以及關(guān)系后,為每張表創(chuàng)建model暴构。如圖所示跪呈,創(chuàng)建完成后左邊文件列表中會(huì)多每張表對(duì)應(yīng)的兩個(gè)文件(iOS7.0以后每張表對(duì)應(yīng)四個(gè)文件)
4.至此,CoreData基本配置已經(jīng)完成取逾,User表和Department表之間也已經(jīng)建立起聯(lián)系耗绿,點(diǎn)擊style可以看到每張表的Attributes及表之間Relationships。
5.如果你熟悉Sqlite砾隅,那么接下來我們要做的就是鍵入代碼進(jìn)行數(shù)據(jù)的增刪改查误阻,實(shí)現(xiàn)增刪改查之前,我們需要先認(rèn)識(shí)幾個(gè)核心類晴埂。
NSManagedObjectContext: (管理對(duì)象上下文) 負(fù)責(zé)應(yīng)用和數(shù)據(jù)庫之間的交互
NSPersistentStoreCoordinator: (持久化存儲(chǔ)協(xié)調(diào)器)處理數(shù)據(jù)存儲(chǔ)的連接
NSManagedObjectModel: (對(duì)象模型) 代表CoreData模型文件,相當(dāng)于實(shí)體
NSEntityDescription: (實(shí)體結(jié)構(gòu)) 用來描述實(shí)體
NSPredicate: (查詢條件) 相當(dāng)于Sqlite中的Sql語句
NSFetchRequest: (數(shù)據(jù)請(qǐng)求) 可以給request設(shè)置請(qǐng)求的條件
如何對(duì)已經(jīng)建好實(shí)體表進(jìn)行增刪改查究反?最好的方法是自己再封裝一個(gè)單利類,該類能夠提供表名查詢方法儒洛,傳入某張表名后奴紧,我們便可以利用上述的幾個(gè)核心類提供的一系列方法創(chuàng)建數(shù)據(jù)的實(shí)例變量并讀寫進(jìn)表內(nèi),如果你是新建的工程項(xiàng)目并勾選了Use Core Data選項(xiàng)晶丘,那么AppDelegate類會(huì)自動(dòng)生成一些CoreData的管理方法(這些方法在你自己封裝過程中可以借鑒)供我們直接使用∈虻現(xiàn)僅以User表舉例實(shí)現(xiàn)增刪改查,以下.m文件里包含了四個(gè)xib創(chuàng)建的Button的回調(diào)浅浮,當(dāng)你點(diǎn)擊按鈕后沫浆,分別會(huì)向User表增刪改查某個(gè)用戶的個(gè)人信息。
//ViewController.m
#import "ViewController.h"
#import "User.h"
#import "AppDelegate.h"
#import <CoreData/CoreData.h>
@interface ViewController()
@property(nonatomic,strong)AppDelegate *app;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.app = [UIApplication sharedApplication].delegate;
}
//insert增
- (IBAction)coreDataInsert {
//1.初始化一個(gè)user數(shù)據(jù)
User *user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:self.app.managedObjectContext];
user.name = @"lcc";
user.sex= @"boy";
user.age = @15;
//2.保存
[self.app.managedObjectContext save:nil];
}
//delete刪
- (IBAction) coreDataDelete {
//1.讀取所有用戶
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:self.app.managedObjectContext];
//2.建立請(qǐng)求
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entity];
//3.設(shè)置查找條件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name =%@",@"lcc"];
[request setPredicate:predicate];
//4.遍歷user表滚秩,找到該用戶后专执,刪除對(duì)象
NSArray *array = [self.app.managedObjectContext executeFetchRequest:request error:nil];
if(array.count){
for(User *newUser in array){
//刪除該用戶
[self.app.managedObjectContext deleteObject:newUser];
}
//保存結(jié)果
[self.app.managedObjectContext save:nil];
NSLog(@"刪除成功");
}else{
NSLog(@"未找到該用戶數(shù)據(jù)");
}
}
//upDate改
- (IBAction) coreDataUpdate {
//1.讀取所有用戶
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:self.app.managedObjectContext];
//2.建立請(qǐng)求
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entity];
//3.設(shè)置查找條件
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name = %@",@"lcc"];
[request setPredicate:predicate];
//4.遍歷user表,找到該用戶后郁油,修改對(duì)象
NSArray *array = [self.app.managedObjectContext executeFetchRequest:request error:nil];
if(array.count){
for(User *newUser in array){
//修改該用戶
newUser.name = @"lcc2";
}
//保存結(jié)果
[self.app.managedObjectContext save:nil];
NSLog(@"修改成功");
}else{
NSLog(@"未找到該用戶數(shù)據(jù)");
}
}
//select查
- (IBAction) coreDataSelect {
//1.讀取User表
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User"inManagedObjectContext:self.app.managedObjectContext];
//2.建立請(qǐng)求
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entity];
//3.遍歷所有用戶本股,取出相關(guān)用戶
NSArray *array = [self.app.managedObjectContext executeFetchRequest:request error:nil];
for (User *user in array){
NSLog(@"%@",user.name);
}
}
@end
如此一來,我們便可以實(shí)現(xiàn)已經(jīng)實(shí)體化的User表的增刪改查桐腌,當(dāng)然拄显,這里的增刪改查相當(dāng)簡(jiǎn)便,實(shí)際項(xiàng)目中要進(jìn)行各種情況判斷案站,數(shù)據(jù)庫操作成功失敗會(huì)有返回信息躬审,該信息應(yīng)該反饋給用戶。此外,因?yàn)镃oreData并不是線程安全的承边,如果你希望自己封裝一個(gè)單利類遭殉,那么必須要考慮到數(shù)據(jù)庫的并發(fā)操作。
為什么要使用CoreData博助?
- CoreData脫離了Sql語句险污,集成化更高。實(shí)際上富岳,一個(gè)成熟的工程中一定是對(duì)數(shù)據(jù)持久化進(jìn)行了封裝的蛔糯,應(yīng)該避免在業(yè)務(wù)邏輯中直接編寫Sql語句。
- CoreData對(duì)版本遷移支持的較好城瞎,App升級(jí)之后數(shù)據(jù)庫字段或者表有更改會(huì)導(dǎo)致crash渤闷,CoreData的版本管理和數(shù)據(jù)遷移變得非常有用疾瓮,手動(dòng)寫sql語句操作相對(duì)麻煩一些脖镀。
- CoreData不光能操縱SQLite,CoreData和iCloud的結(jié)合也很好狼电,如果有這方面需求的話優(yōu)先考慮CoreData蜒灰。
- CoreData是支持多線程的,但需要thread confinement的方式實(shí)現(xiàn)肩碟,使用了多線程之后可以最大化的防止阻塞主線程强窖。
四.Sqlite(關(guān)鍵詞:靈活)
什么是Sqlite?
Sqlite是iPhone自帶的的數(shù)據(jù)庫管理系統(tǒng)。如果你對(duì)數(shù)據(jù)庫和Sql語句不陌生削祈,那么在介紹完CoreData后翅溺,你一定不滿足CoreData,作為一個(gè)程序員髓抑,也許你更希望能夠直接操作數(shù)據(jù)庫咙崎。既然蘋果選擇Sqlite作為數(shù)據(jù)庫,自然也提供了一系列可以直接操作它的函數(shù)(C語言函數(shù))吨拍,利用這些函數(shù)你完全能夠自己封裝一個(gè)sqlite數(shù)據(jù)庫框架褪猛,但同時(shí)你必須熟悉sql語句以及C語言語法。我在gitHub上上傳了一個(gè)自己封裝的輕量級(jí)Sqlite框架LCCSqliteManager 羹饰。目前版本只實(shí)現(xiàn)了一些基本的數(shù)據(jù)庫功能伊滋,如果感興趣你可以參考一下。
如何直接操作Sqlite队秩?
libsqlite3.0這個(gè)函數(shù)庫提供了許多C語言函數(shù)用于直接操作Sqlite笑旺,你的項(xiàng)目中導(dǎo)入該函數(shù)庫即可,具體導(dǎo)入方法LCCSqliteManager的文檔中有說明馍资,這里不在過多詳述燥撞,僅介紹幾個(gè)核心函數(shù)。
/*
** 打開或創(chuàng)建數(shù)據(jù)庫
* char * filePath : 文件路徑 (UTF-8)
* sqlite3 **ppDb : 指向數(shù)據(jù)庫文件指針的指針
*/
int sqlite3_open(const char* filePath, sqlite3** ppDb);
//你可以這樣調(diào)用該函數(shù)
sqlite3 *_sqlite;
NSString *filePath = [NSHomeDirectory()stringByAppendingFormat: @"/Documents/%@.sqlite",filename];
int result = sqlite3_open([filePath UTF8String], &_sqlite);
if (result != SQLITE_OK) {
NSLog(@"error:數(shù)據(jù)庫%@打開失敗",filename)
}
/*
** 關(guān)閉數(shù)據(jù)庫
sqlite3* : 指向該數(shù)據(jù)庫的指針
*/
int sqlite3_close(sqlite3*);
/*
** 創(chuàng)建一張表
*/
SQLITE_API int SQLITE_STDCALL sqlite3_exec(
sqlite3*, /* 非空數(shù)據(jù)庫指針 */
const char *sql, /* 用于創(chuàng)建表的SQL語句 */
int (*callback)(void*,int,char**,char**), /* 回調(diào)函數(shù) */
void *, /* 第一個(gè)參數(shù)回調(diào) */
char **errmsg /* 錯(cuò)誤信息 */
);
//第三、四個(gè)參數(shù)一般傳入NULL即可物舒。你可以這樣調(diào)用該函數(shù)
char *error = NULL;
sqlite3 *_sqlite;
NSString *targetSql = @"CREATE TABLE User(name TEXT,age INT,SEX TEXT)"
int result = sqlite3_exec(_sqlite, [targetSql UTF8String], NULL, NULL, &error);
if (result != SQLITE_OK) {
NSLog(@"創(chuàng)建表失敗:%s", error);
return NO;
}
/*
** 預(yù)編譯sql語句
*/
int sqlite3_prepare_v2(
sqlite3 *db, /* 非空數(shù)據(jù)庫指針 */
const char *zSql, /* 要執(zhí)行的SQL語句 */
int nByte, /* zSql字節(jié)的最大長(zhǎng)度 */
sqlite3_stmt **ppStmt, /* 能夠使用sqlite3_step()執(zhí)行的編譯好的準(zhǔn)備語句的指針 */
const char **pzTail /* 超過zSql最大長(zhǎng)度的的剩余部分*/
);
/*
** 執(zhí)行預(yù)編譯好的sql語句色洞。
*/
int sqlite3_step(sqlite3_stmt*);
/*
** 釋放內(nèi)存。
*/
int sqlite3_finalize(sqlite3_stmt *pStmt);
上述三個(gè)方法配套使用冠胯,一般在含有查找操作的sql語句時(shí)都需要預(yù)編譯后再執(zhí)行火诸,并且手動(dòng)釋放內(nèi)存。
上面的方法比較常用荠察,表的刪除和修改操作因?yàn)槎夹枰檎也僮髦檬瘢孕枰猻qlite3_prepare_v2 和sqlite3_step配合使用,sqlite3_exec同樣可以執(zhí)行sql語句悉盆,因?yàn)樵黾雍筒檎覕?shù)據(jù)一般不需要預(yù)編譯盯荤。兩者具體的區(qū)別這里不在過多闡述,讀者可以根據(jù)參數(shù)的不同自行理解焕盟,或者查找網(wǎng)上有關(guān)libsqlite3.0函數(shù)庫的相關(guān)技術(shù)博客秋秤,該函數(shù)庫的大部分函數(shù)以及參數(shù)作用都有詳細(xì)解釋。
Sqlite和CoreData相比的優(yōu)劣
- 直接操作Sqlite更容易理解數(shù)據(jù)的存儲(chǔ)方式脚翘,靈活度更高灼卢。
- 在大量數(shù)據(jù)的批量讀寫速度上,Sqlite占有優(yōu)勢(shì)来农。
- Sqlite需要自己寫Sql語句鞋真,且多線程、批量操作等都需要代碼實(shí)現(xiàn)沃于。
總結(jié)
本文大致介紹了iOS的四種數(shù)據(jù)持久化方式涩咖,且對(duì)他們之間的關(guān)系進(jìn)行了一定講解,筆者在封裝過LCCSqliteManager后繁莹,反而更傾向于使用CoreData檩互,因?yàn)槠涓€(wěn)定便捷,而大部分項(xiàng)目中蒋困,NSUSerDefaults與CoreData基本可以滿足數(shù)據(jù)持久化需求盾似,所以筆者較為推薦。
最后感謝閱讀雪标,也歡迎提出寶貴意見或糾正錯(cuò)誤零院。