iOS開(kāi)發(fā)簡(jiǎn)單高效的數(shù)據(jù)存儲(chǔ)

??????? 在iOS開(kāi)發(fā)過(guò)程中,不管是做什么應(yīng)用脚猾,都會(huì)碰到數(shù)據(jù)保存的問(wèn)題葱峡,你是用什么方法來(lái)持久保存數(shù)據(jù)的?這是在幾乎每一次關(guān)于iOS技術(shù)的交流或討論都會(huì)被提到的問(wèn)題龙助,而且大家對(duì)這個(gè)問(wèn)題的熱情持續(xù)高漲族沃。本文主要從概念上把“數(shù)據(jù)存儲(chǔ)”這個(gè)問(wèn)題進(jìn)行剖析,并且結(jié)合各自特點(diǎn)和適用場(chǎng)景進(jìn)行全面拋析。脆淹。

NSUserDefaults

?????? NSUserDefaults被設(shè)計(jì)用來(lái)存儲(chǔ)設(shè)備和應(yīng)用的配置信息常空,它通過(guò)一個(gè)工廠方法返回默認(rèn)的、也是最常用到的實(shí)例對(duì)象盖溺。這個(gè)對(duì)象中儲(chǔ)存了系統(tǒng)中用戶的配置信息漓糙,開(kāi)發(fā)者可以通過(guò)這個(gè)實(shí)例對(duì)象對(duì)這些已有的信息進(jìn)行修改,也可以按照自己的需求創(chuàng)建新的配置項(xiàng)烘嘱。

?????? NSUserDefaults可以存儲(chǔ)的數(shù)據(jù)類型包括:NSData昆禽、NSString、NSNumber蝇庭、NSDate醉鳖、NSArray、NSDictionary哮内。如果要存儲(chǔ)其他類型盗棵,則需要轉(zhuǎn)換為前面的類型,才能用NSUserDefaults存儲(chǔ)北发。具體實(shí)現(xiàn)為:

保存數(shù)據(jù):

NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults];

NSString *name =@”default string“;[defaults setObject:firstName forKey:@"name"];?

//獲得UIImage實(shí)例

UIImage *image=[[UIImage alloc]initWithContentsOfFile:@"photo.jpg"];

NSData *imageData = UIImageJPEGRepresentation(image, 100);//UIImage對(duì)象轉(zhuǎn)換成NSData

[defaults synchronize]; //用synchronize方法把數(shù)據(jù)持久化到standardUserDefaults數(shù)據(jù)庫(kù)

讀取數(shù)據(jù):

NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults];

NSString *name = [defaults objectForKey:@"name"]; //根據(jù)鍵值取出name

NSData *imageData = [defaults dataForKey:@"image"];

UIImage *Image = [UIImage imageWithData:imageData]; //NSData轉(zhuǎn)換為UIImage

歸檔纹因,解歸檔

NSKeyedArchiver:采用歸檔的形式來(lái)保存數(shù)據(jù),該數(shù)據(jù)對(duì)象需要遵守NSCoding協(xié)議琳拨,并且該對(duì)象對(duì)應(yīng)的類必須提供encodeWithCoder:和initWithCoder:方法瞭恰。前一個(gè)方法告訴系統(tǒng)怎么對(duì)對(duì)象進(jìn)行編碼,而后一個(gè)方法則是告訴系統(tǒng)怎么對(duì)對(duì)象進(jìn)行解碼狱庇。例如對(duì)Possession對(duì)象歸檔保存惊畏。

定義Possession:@interface Possession:NSObject{//遵守NSCoding協(xié)議

NSString *name;//待歸檔類型

}

@implementation Possession

-(void)encodeWithCoder:(NSCoder *)aCoder{

[aCoder encodeObject:name forKey:@"name"];

}

-(void)initWithCoder:(NSCoder *)aDecoder{

name=[[aDeCoder decodeObjectforKey:@"name"] retain];

}

歸檔操作:

如果對(duì)Possession對(duì)象allPossession歸檔保存,只需要NSCoder子類NSKeyedArchiver的方法archiveRootObject:toFile: 即可密任。

NSString *path = [self possessionArchivePath];

[NSKeyedArchiver archiveRootObject:allPossessions toFile: path ]

解壓操作:

同樣調(diào)用NSCoder子類NSKeyedArchiver的方法unarchiveRootObject:toFile: 即可

allPossessions = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] retain];

缺點(diǎn):歸檔的形式來(lái)保存數(shù)據(jù)陕截,只能一次性歸檔保存以及一次性解壓。所以只能針對(duì)小量數(shù)據(jù)批什,而且對(duì)數(shù)據(jù)操作比較笨拙农曲,即如果想改動(dòng)數(shù)據(jù)的某一小部分,還是需要解壓整個(gè)數(shù)據(jù)或者歸檔整個(gè)數(shù)據(jù)驻债。

SQLite

用SQLite存儲(chǔ)查詢需求較多的數(shù)據(jù),是我們開(kāi)發(fā)中最常見(jiàn)的一種方式乳规,例如app的界面數(shù)據(jù)緩存,離線緩存等合呐。

第一步:需要添加SQLite相關(guān)的庫(kù)以及頭文件:在項(xiàng)目文件的Build Phases下暮的,找到Link Binary Library(ies),添加libsqlite3.0.dylib

(libsqlite3.dylib與前者的區(qū)別暫時(shí)不知淌实,兩者應(yīng)該差不多)冻辩;在項(xiàng)目文件中頭文件或者源文件中添加頭文件#import “/usr/include/sqlite3.h”

第二步:開(kāi)始使用SQLite:

NSArray *documentsPaths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask , YES);

NSString *databaseFilePath=[[documentsPaths objectAtIndex:0] stringByAppendingPathComponent:@"mydb"];

//上面兩句已經(jīng)比較熟悉了吧猖腕!

//打開(kāi)數(shù)據(jù)庫(kù)

if (sqlite3_open([databaseFilePath UTF8String], &database)==SQLITE_OK) {

NSLog(@"sqlite dadabase is opened.");

}

else{ return;}//打開(kāi)不成功就返回

在打開(kāi)了數(shù)據(jù)庫(kù)的前提下,如果數(shù)據(jù)庫(kù)沒(méi)有表恨闪,那就開(kāi)始建表了哦倘感!

char *error;

const char *createSql="create table(id integer primary key autoincrement, name text)";

if (sqlite3_exec(database, createSql, NULL, NULL, &error)==SQLITE_OK) {

NSLog(@"create table is ok.");

}

else

{

NSLog(@"error: %s",error);

sqlite3_free(error);//每次使用完畢清空error字符串,提供給下一次使用

}

建表完成之后咙咽,就開(kāi)始插入記錄:

const char *insertSql="insert into a person (name) values(‘gg’)";

if (sqlite3_exec(database, insertSql, NULL, NULL, &error)==SQLITE_OK) {

NSLog(@"insert operation is ok.");

}

else

{

NSLog(@"error: %s",error);

sqlite3_free(error);//每次使用完畢清空error字符串老玛,提供給下一次使用

}

下一步,查詢記錄:

const char *selectSql="select id,name from a person";

sqlite3_stmt *statement;

if (sqlite3_prepare_v2(database,selectSql, -1, &statement, nil)==SQLITE_OK) {

NSLog(@"select operation is ok.");

}

else

{

NSLog(@"error: %s",error);

sqlite3_free(error);

}

while(sqlite3_step(statement)==SQLITE_ROW) {

int _id=sqlite3_column_int(statement, 0);

NSString *name=(char*)sqlite3_column_text(statement, 1);

NSLog(@"row>>id %i, name %s",_id,name);

}

sqlite3_finalize(statement);

最后钧敞,關(guān)閉數(shù)據(jù)庫(kù):

sqlite3_close(database);

注意:寫(xiě)入數(shù)據(jù)庫(kù)蜡豹,字符串可以采用char方式,而從數(shù)據(jù)庫(kù)中取出char類型溉苛,當(dāng)char類型有表示中文字符時(shí)镜廉,會(huì)出現(xiàn)亂碼。這是因?yàn)閿?shù)據(jù)庫(kù)默認(rèn)使用ascII編碼方式愚战。所以要想正確從數(shù)據(jù)庫(kù)中取出中文娇唯,需要用NSString來(lái)接收從數(shù)據(jù)庫(kù)取出的字符串。

CoreData

Core Data使用起來(lái)相對(duì)直接使用SQLite3的API而言更加的面向?qū)ο蠓锞蓿僮鬟^(guò)程通常分為以下幾個(gè)步驟:

1.創(chuàng)建管理上下文

創(chuàng)建管理上下可以細(xì)分為:加載模型文件->指定數(shù)據(jù)存儲(chǔ)路徑->創(chuàng)建對(duì)應(yīng)數(shù)據(jù)類型的存儲(chǔ)->創(chuàng)建管理對(duì)象上下方并指定存儲(chǔ)视乐。

經(jīng)過(guò)這幾個(gè)步驟之后可以得到管理對(duì)象上下文NSManagedObjectContext洛搀,以后所有的數(shù)據(jù)操作都由此對(duì)象負(fù)責(zé)敢茁。同時(shí)如果是第一次創(chuàng)建上下文,Core Data會(huì)自動(dòng)創(chuàng)建存儲(chǔ)文件(例如這里使用SQLite3存儲(chǔ))留美,并且根據(jù)模型對(duì)象創(chuàng)建對(duì)應(yīng)的表結(jié)構(gòu)彰檬。下圖為第一次運(yùn)行生成的數(shù)據(jù)庫(kù)及相關(guān)映射文件:

為了方便后面使用,NSManagedObjectContext對(duì)象可以作為單例或靜態(tài)屬性來(lái)保存谎砾,下面是創(chuàng)建的管理對(duì)象上下文的主要代碼:

-(NSManagedObjectContext *)createDbContext{

NSManagedObjectContext *context;

//打開(kāi)模型文件逢倍,參數(shù)為nil則打開(kāi)包中所有模型文件并合并成一個(gè)

NSManagedObjectModel *model=[NSManagedObjectModel mergedModelFromBundles:nil];

//創(chuàng)建解析器

NSPersistentStoreCoordinator *storeCoordinator=[[NSPersistentStoreCoordinator alloc]initWithManagedObjectModel:model];

//創(chuàng)建數(shù)據(jù)庫(kù)保存路徑

NSString *dir=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];

NSLog(@"%@",dir);

NSString *path=[dir stringByAppendingPathComponent:@"myDatabase.db"];

NSURL *url=[NSURL fileURLWithPath:path];

//添加SQLite持久存儲(chǔ)到解析器

NSError *error;

[storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error];

if(error){

NSLog(@"數(shù)據(jù)庫(kù)打開(kāi)失敗景图!錯(cuò)誤:%@",error.localizedDescription);

}else{

context=[[NSManagedObjectContext alloc]init];

context.persistentStoreCoordinator=storeCoordinator;

NSLog(@"數(shù)據(jù)庫(kù)打開(kāi)成功较雕!");

}

return context;

}

2.查詢數(shù)據(jù)

對(duì)于有條件的查詢,在Core Data中是通過(guò)謂詞來(lái)實(shí)現(xiàn)的挚币。首先創(chuàng)建一個(gè)請(qǐng)求亮蒋,然后設(shè)置請(qǐng)求條件,最后調(diào)用上下文執(zhí)行請(qǐng)求的方法妆毕。

-(void)addUserWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city{

//添加一個(gè)對(duì)象

User *us= [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:self.context];

us.name=name;

us.screenName=screenName;

us.profileImageUrl=profileImageUrl;

us.mbtype=mbtype;

us.city=city;

NSError *error;

//保存上下文

if (![self.context save:&error]) {

NSLog(@"添加過(guò)程中發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@慎玖!",error.localizedDescription);

}

}

如果有多個(gè)條件,只要使用謂詞組合即可笛粘,那么對(duì)于關(guān)聯(lián)對(duì)象條件怎么查詢呢趁怔?這里分為兩種情況進(jìn)行介紹:

a.查找一個(gè)對(duì)象只有唯一一個(gè)關(guān)聯(lián)對(duì)象的情況湿硝,例如查找用戶名為“Binger”的微博(一個(gè)微博只能屬于一個(gè)用戶),通過(guò) -

(NSArray )getStatusesByUserName:(NSString )name{

NSFetchRequest *request=[NSFetchRequest fetchRequestWithEntityName:@”Status”];

request.predicate=[NSPredicate predicateWithFormat:@”user.name=%@”,name];

NSArray *array=[self.context executeFetchRequest:request error:nil];

return array; }ata生成的SQL語(yǔ)句會(huì)發(fā)現(xiàn)其實(shí)就是把Status表和User表進(jìn)行了關(guān)聯(lián)查詢(JOIN連接)润努。

b.查找一個(gè)對(duì)象有多個(gè)關(guān)聯(lián)對(duì)象的情況关斜,例如查找發(fā)送微博內(nèi)容中包含“Watch”并且用戶昵稱為“小娜”的用戶(一個(gè)用戶有多條微博),此時(shí)可以充分利用謂詞進(jìn)行

-(NSArray )getUsersByStatusText:(NSString )text screenName:(NSString *)screenName{

NSFetchRequest *request=[NSFetchRequest fetchRequestWithEntityName:@”Status”];

request.predicate=[NSPredicate predicateWithFormat:@”text LIKE ‘Watch‘”,text];

NSArray *statuses=[self.context executeFetchRequest:request error:nil];

NSPredicate *userPredicate= [NSPredicate predicateWithFormat:@"user.screenName=%@",screenName];

NSArray *users= [statuses filteredArrayUsingPredicate:userPredicate];

return users;

如果單純查找微博中包含“Watch”的用戶任连,直接查出對(duì)應(yīng)的微博蚤吹,然后通過(guò)每個(gè)微博的user屬性即可獲得用戶,此時(shí)就不用使用額外的謂詞過(guò)濾條件随抠。

3.插入數(shù)據(jù)

插入數(shù)據(jù)需要調(diào)用實(shí)體描述對(duì)象NSEntityDescription返回一個(gè)實(shí)體對(duì)象裁着,然后設(shè)置對(duì)象屬性,最后保存當(dāng)前上下文即可拱她。這里需要注意二驰,增、刪秉沼、改操作完最后必須調(diào)用管理對(duì)象上下文的保存方法桶雀,否則操作不會(huì)執(zhí)行。

-(void)addUserWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city{

//添加一個(gè)對(duì)象

User *us= [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:self.context];

us.name=name;

us.screenName=screenName;

us.profileImageUrl=profileImageUrl;

us.mbtype=mbtype;

us.city=city;

NSError *error;

//保存上下文

if (![self.context save:&error]) {

NSLog(@"添加過(guò)程中發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@唬复!",error.localizedDescription);

}

}

4.刪除數(shù)據(jù)

刪除數(shù)據(jù)可以直接調(diào)用管理對(duì)象上下文的deleteObject方法矗积,刪除完保存上下文即可。注意敞咧,刪除數(shù)據(jù)前必須先查詢到對(duì)應(yīng)對(duì)象棘捣。

-(void)removeUser:(User *)user{

[self.context deleteObject:user];

NSError *error;

if (![self.context save:&error]) {

NSLog(@"刪除過(guò)程中發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@!",error.localizedDescription);

}

}

5.修改數(shù)據(jù)

修改數(shù)據(jù)首先也是取出對(duì)應(yīng)的實(shí)體對(duì)象休建,然后通過(guò)修改對(duì)象的屬性乍恐,最后保存上下文。

-(void)modifyUserWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city{

User *us=[self getUserByName:name];

us.screenName=screenName;

us.profileImageUrl=profileImageUrl;

us.mbtype=mbtype;

us.city=city;

NSError *error;

if (![self.context save:&error]) {

NSLog(@"修改過(guò)程中發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription);

}

}

調(diào)試

雖然Core Data(如果使用SQLite數(shù)據(jù)庫(kù))操作最終轉(zhuǎn)換為SQL操作测砂,但是調(diào)試起來(lái)卻不想操作SQL那么方便茵烈。特別是對(duì)于初學(xué)者而言經(jīng)常出現(xiàn)查詢報(bào)錯(cuò)的問(wèn)題,如果能看到最終生成的SQL語(yǔ)句自然對(duì)于調(diào)試很有幫助砌些。事實(shí)上在Xcode中是支持Core Data調(diào)試的呜投,具體操作:Product-Scheme-Edit Scheme-Run-Arguments中依次添加兩個(gè)參數(shù)(注意參數(shù)順序不能錯(cuò)):-com.apple.CoreData.SQLDebug、1存璃。然后在運(yùn)行程序過(guò)程中如果操作了數(shù)據(jù)庫(kù)就會(huì)將SQL語(yǔ)句打印在輸出面板仑荐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市有巧,隨后出現(xiàn)的幾起案子释漆,更是在濱河造成了極大的恐慌,老刑警劉巖篮迎,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件男图,死亡現(xiàn)場(chǎng)離奇詭異示姿,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)逊笆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)栈戳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人难裆,你說(shuō)我怎么就攤上這事子檀。” “怎么了乃戈?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵褂痰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我症虑,道長(zhǎng)缩歪,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任谍憔,我火速辦了婚禮匪蝙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘习贫。我一直安慰自己逛球,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布苫昌。 她就那樣靜靜地躺著颤绕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蜡歹。 梳的紋絲不亂的頭發(fā)上屋厘,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天涕烧,我揣著相機(jī)與錄音月而,去河邊找鬼。 笑死议纯,一個(gè)胖子當(dāng)著我的面吹牛父款,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瞻凤,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼憨攒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了阀参?” 一聲冷哼從身側(cè)響起肝集,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛛壳,沒(méi)想到半個(gè)月后杏瞻,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體所刀,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年捞挥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浮创。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡砌函,死狀恐怖斩披,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情讹俊,我是刑警寧澤垦沉,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站仍劈,受9級(jí)特大地震影響乡话,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜耳奕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一绑青、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屋群,春花似錦闸婴、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至对竣,卻和暖如春庇楞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背否纬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工吕晌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人临燃。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓睛驳,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親膜廊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子乏沸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容