iOS开发简单高效的数据存储
?
?
在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è)工廠(chǎng)方法返回默認(rèn)的、也是最常用到的實(shí)例對(duì)象。這個(gè)對(duì)象中儲(chǔ)存了系統(tǒng)中用戶(hù)的配置信息,開(kāi)發(fā)者可以通過(guò)這個(gè)實(shí)例對(duì)象對(duì)這些已有的信息進(jìn)行修改,也可以按照自己的需求創(chuàng)建新的配置項(xiàng)。?
?
NSUserDefaults可以存儲(chǔ)的數(shù)據(jù)類(lèi)型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要存儲(chǔ)其他類(lèi)型,則需要轉(zhuǎn)換為前面的類(lèi)型,才能用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)的類(lèi)必須提供encodeWithCoder:和initWithCoder:方法。前一個(gè)方法告訴系統(tǒng)怎么對(duì)對(duì)象進(jìn)行編碼,而后一個(gè)方法則是告訴系統(tǒng)怎么對(duì)對(duì)象進(jìn)行解碼。例如對(duì)Possession對(duì)象歸檔保存。?
?
定義Possession:
?
@interface Possession:NSObject<NSCoding>{//遵守NSCoding協(xié)議
? ? ? ?NSString *name;//待歸檔類(lèi)型
}
@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子類(lèi)NSKeyedArchiver的方法archiveRootObject:toFile: 即可。
?
NSString *path = [self possessionArchivePath];
[NSKeyedArchiver archiveRootObject:allPossessions toFile: path ]
?
解壓操作:
?
?
同樣調(diào)用NSCoder子類(lèi)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ǔ)查詢(xún)需求較多的數(shù)據(jù),是我們開(kāi)發(fā)中最常見(jiàn)的一種方式,例如app的界面數(shù)據(jù)緩存,離線(xiàn)緩存等。?
?
第一步:需要添加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字符串,提供給下一次使用
}?
?
下一步,查詢(xún)記錄:
?
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類(lèi)型,當(dāng)char類(lèi)型有表示中文字符時(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ū)ο?#xff0c;操作過(guò)程通常分為以下幾個(gè)步驟:
?
1.創(chuàng)建管理上下文
?
創(chuàng)建管理上下可以細(xì)分為:加載模型文件->指定數(shù)據(jù)存儲(chǔ)路徑->創(chuàng)建對(duì)應(yīng)數(shù)據(jù)類(lèi)型的存儲(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.查詢(xún)數(shù)據(jù)?
?
對(duì)于有條件的查詢(xún),在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ì)象條件怎么查詢(xún)呢?這里分為兩種情況進(jìn)行介紹:
?
a.查找一個(gè)對(duì)象只有唯一一個(gè)關(guān)聯(lián)對(duì)象的情況,例如查找用戶(hù)名為“Binger”的微博(一個(gè)微博只能屬于一個(gè)用戶(hù)),通過(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)查詢(xún)(JOIN連接)。
?
b.查找一個(gè)對(duì)象有多個(gè)關(guān)聯(lián)對(duì)象的情況,例如查找發(fā)送微博內(nèi)容中包含“Watch”并且用戶(hù)昵稱(chēng)為“小娜”的用戶(hù)(一個(gè)用戶(hù)有多條微博),此時(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”的用戶(hù),直接查出對(duì)應(yīng)的微博,然后通過(guò)每個(gè)微博的user屬性即可獲得用戶(hù),此時(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ù)前必須先查詢(xún)到對(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)查詢(xú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ǔ)句打印在輸出面板。
?
轉(zhuǎn)載于:https://www.cnblogs.com/fengmin/p/5464392.html
總結(jié)
以上是生活随笔為你收集整理的iOS开发简单高效的数据存储的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Access denied for us
- 下一篇: 听了一堂《**学院》的课,我也是醉了