IOS中數(shù)據(jù)的持久化保存這塊內(nèi)容诬乞,類似于Android中文件的幾種常見的存儲方式赡盘。
對于數(shù)據(jù)的持久化存儲讲仰,ios中一般提供了4種不同的機(jī)制檐什。
1.屬性列表
2.對象歸檔
3.數(shù)據(jù)庫存儲(SQLite3)
4.蘋果公司提供的持久性工具Core Data牵祟。
其實(shí)儲存的形式無非就這么幾種深夯,而我們還必須要關(guān)心的是,這些文件會被放置在那個文件下,然后如何讀取咕晋。
也就是說:IOS上數(shù)據(jù)存儲雹拄,我們要了解的兩點(diǎn),數(shù)據(jù)存儲格式(也就是存儲機(jī)制)掌呜,數(shù)據(jù)存儲位置滓玖。
1》文件如何存儲(如上面4點(diǎn))
2》文件存儲在哪里。
對于數(shù)據(jù)的操作质蕉,其實(shí)我們關(guān)心的是操作的速率势篡。
就好比在Adnroid中偏好存儲,數(shù)據(jù)庫存儲模暗,io存儲一樣禁悠。
我大致問了我們公司新來的ios哥們,他說他們培訓(xùn)機(jī)構(gòu)基本對數(shù)據(jù)操作這塊就講了屬性列表和數(shù)據(jù)庫兑宇,以及普通的文件存儲(比如音視頻圖這些多媒體數(shù)據(jù))绷蹲。
我就只好先看看書了。
一:應(yīng)用文件目錄
首先我們來看了解下ios數(shù)據(jù)存儲位置顾孽,因?yàn)橹挥兄牢恢寐窂轿覀儾拍苋プx取數(shù)據(jù)祝钢,而數(shù)據(jù)的持久化機(jī)制不過是針對操作速率來考慮的,
比如我們大致知道屬性列表(既鍵值對形式)的存儲熟慮應(yīng)該高于數(shù)據(jù)庫高于io文件流存儲若厚。
我們在選擇用何種機(jī)制存儲數(shù)據(jù)拦英,主要也是看數(shù)據(jù)的形式。
一個ios應(yīng)用安裝后大致會有如下文件夾及其對應(yīng)路徑:
在mac上看模擬器中應(yīng)用路徑:
/Users/nono/Library/Application Support/iPhone Simulator/5.1/Applications/2D135859-1E80-4754-B36D-34A53C521DE3
你在finder中的home下可能找不到Library這個目錄测秸,因?yàn)槊菜剖怯安仄饋砹耍ㄎ疫@機(jī)器上是达箍,在終端可以看到)矮男。
最后那一竄的類似序列號的東西就是ios自動給應(yīng)用生成的一組應(yīng)用唯一識別碼最為了應(yīng)用的home目錄名。
其下面就是上圖所示了。
書上對這些文件夾介紹:
Document:應(yīng)用程序?qū)⑵鋽?shù)據(jù)存儲在這個文件夾下崭放,基于NSUserDefaults的首選項(xiàng)的設(shè)置除外卓舵。
簡單理解是蜈垮,基本上我們要操作的一些數(shù)據(jù)都是存儲在這個文件夾下面的
TIPS:這邊提下一點(diǎn)汰寓,對于ios系統(tǒng)這么分配文件夾,是因?yàn)樵谠O(shè)備進(jìn)行同步時(shí)缠俺,ITunes有選擇性的意識來備份文件显晶。
比如我們可以猜到,tmp下的應(yīng)該就不會備份了壹士。
對于Document文件夾目錄路徑的獲取磷雇,API提供了這么一種方法:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = [paths objectAtIndex:0];
Library:基于NSUserDefault首選項(xiàng)設(shè)置存儲在其下Preferences文件夾中,簡單來說躏救,這個文件夾一般你很少操作到唯笙。
書上對于這部分基本沒介紹。估計(jì)對于初級部分是跳過了。
Tmp:應(yīng)用臨時(shí)存儲文件崩掘,當(dāng)不需要時(shí)尿庐,應(yīng)用負(fù)責(zé)刪除其下的文件數(shù)據(jù)。
該文件也提供了目錄獲取方法:
NSString *tmpDoc = NSTemporaryDirectory();
應(yīng)用程序文件:這個基本沒提到書上呢堰,但是我們大致可以猜測,這就是整個應(yīng)用程序的程序文件夾吧凡泣。
好了枉疼,以上我們大致解決了我們提到的第一個點(diǎn),文件存儲目錄
二:數(shù)據(jù)存儲機(jī)制
1.屬性列表 Write寫入方式
這個其實(shí)我們早見過鞋拟,plist就是骂维,感覺用來存儲鍵值對小數(shù)據(jù)是最合適,因?yàn)樗俾屎芨摺?/p>
這個存儲機(jī)制很簡單贺纲,對于前面我們使用過了在plist文件來讀取數(shù)據(jù)填充一些列表航闺,只不過那會plist文件存儲位置不同,
用的是Mainbundle什么的來返回文件夾猴誊,其實(shí)這邊我也推測潦刃,上面提到有個應(yīng)用程序文件夾,它下面的文件就是這么來讀取的~(反正暫時(shí)不管他)
這邊不過就是改變了存儲位置懈叹,數(shù)據(jù)操作還是一樣的
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = [paths objectAtIndex:0];
NSString *myFile = [docPath stringByAppendingPathComponent:@"my.list"];
//讀取文件
NSArray *array = [[NSArray alloc] initWithContentsOfFile:myFile];
//操作完若修改了數(shù)據(jù)則乖杠,寫入文件
[array writeToFile:myFile atomically:YES];
2.對象歸檔
上面的屬性列表存儲機(jī)制,我們都知道澄成,這個機(jī)制支持NSArray,NSDictionary,NSData,NSString,NSNumber,NSDate 等等
這些對象直接寫入plist文件中胧洒。
那么對于一些復(fù)雜對象,我要保存整個這個對象數(shù)據(jù)呢墨状?
反正我是這么覺得卫漫,這個機(jī)制很像java中的對象整體序列化。當(dāng)然肾砂,這些數(shù)據(jù)在讀取是就需要遵循一種墨守成規(guī)的協(xié)議了列赎。
首先我們定義的對象類,必須實(shí)現(xiàn)NSCoding和NSCopying協(xié)議(額镐确,網(wǎng)上說后面這個不實(shí)現(xiàn)也可以粥谬,我猜是他對象沒有copy操作,因此沒出錯)書本上反正是實(shí)現(xiàn)了這兩個協(xié)議
然后歸檔中用到的操作類
NSKeyedArchiver
這邊我們定義一個對象辫塌,h文件中定義兩屬性漏策,申明要實(shí)現(xiàn)的NSCoding和NSCopying協(xié)議
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject <NSCoding>
@property (nonatomic,retain)NSString *gender; // 性別
@property (nonatomic,retain)NSString *name; // 姓名
@property (nonatomic,assign)int age; // 年齡
@end
Person.m
#import "Person.h"
@implementation Person
// 歸檔 實(shí)際上就是將當(dāng)前類的屬性編碼為NSData類型
- (void)encodeWithCoder:(NSCoder *)aCoder{
// 實(shí)際編碼過程,原理就是將name這個屬性的值編碼為NSData類型臼氨,因?yàn)槲覀兘獯a的時(shí)候掺喻,需要重新為該類屬性賦值,所以需要加標(biāo)記,也就是Key感耙。
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeObject:self.gender forKey:@"gender"];
[aCoder encodeInt:self.age forKey:@"age"];
NSLog(@"執(zhí)行了歸檔的方法");
}
// 反歸檔 因?yàn)闅w檔的過程中褂乍,我們是將當(dāng)前類轉(zhuǎn)換為NSData類型,并且儲存到了某個文件中即硼,當(dāng)我們從文件中讀取出來數(shù)據(jù)的時(shí)候逃片,基礎(chǔ)類型,例如:NSArray等都有initWithContentsOfFile的方法來初始化只酥,但是復(fù)雜類型沒有類似的方法褥实,只能是反歸檔來完成此事。
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
// 將剛才編碼為NSData類型的屬性裂允,又通過解碼方式變回原來的類型损离,上面編碼過程中,所賦給的key值為何種名稱绝编,底下的解碼得對應(yīng)上僻澎。
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntForKey:@"age"];
self.gender = [aDecoder decodeObjectForKey:@"gender"];
NSLog(@"執(zhí)行了反歸檔的方法");
}
return self;
}
@end
RootViewController.m
#import "RootViewController.h"
#import "SandBoxPaths.h"
#import "Person.h"
@interface RootViewController ()
@end
@implementation RootViewController
#pragma mark -------- 歸檔
// 歸檔并存入沙盒中
- (void)archiverAndSaveSandBox{
// 歸檔實(shí)際上就是將Person對象轉(zhuǎn)換為NSData類型的數(shù)據(jù)
Person *person = [[Person alloc]init];
person.name = @"廈航";
person.age = 24;
person.gender = @"女";
// 歸檔的時(shí)候,實(shí)際是將復(fù)雜類對象的屬性一一轉(zhuǎn)換為NSData類型十饥,所以是逐步轉(zhuǎn)換的窟勃,最終需要將每一步轉(zhuǎn)換好的NSData類型組裝為一個完整的NSData,所以我們需要一個可變的NSData來接收它
NSMutableData *receiveData = [[NSMutableData alloc]init];
//=========================================================================
// 歸檔操作需要借助系統(tǒng)的一個歸檔工具類來實(shí)現(xiàn)逗堵,這個類實(shí)際的操作就是將Person對象轉(zhuǎn)換為NSData類型的數(shù)據(jù)拳恋,并賦值給剛才咱們初始化的NSData對象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:receiveData];
//=========================================================================
// 歸檔開始
[archiver encodeObject:person forKey:@"person"];
// 需要有一個標(biāo)志,讓我們知道歸檔完成了砸捏,我們的receiveData中有值了 (不然會出錯誤)
[archiver finishEncoding];
// 已經(jīng)轉(zhuǎn)換完成的谬运,就可以進(jìn)行數(shù)據(jù)持久化了
NSString *pathString = [[SandBoxPaths documentsPath]stringByAppendingPathComponent:@"person.DA"];
[receiveData writeToFile:pathString atomically:YES];
NSLog(@"-------%@",pathString);
}
#pragma mark ----- 反歸檔
- (void)unArichiver{
// 反歸檔,實(shí)際上就是將NSData類型轉(zhuǎn)換為復(fù)雜類型對象垦藏,就是本例中的person對象
NSString *pathString = [[SandBoxPaths documentsPath]stringByAppendingPathComponent:@"person.DA"];
NSData *data = [[NSData alloc]initWithContentsOfFile:pathString];
// 反歸檔梆暖,反歸檔也需要借助系統(tǒng)的一個反歸檔工具類來實(shí)現(xiàn)
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data];
// 開始反歸檔
Person *person = [unarchiver decodeObjectForKey:@"person"];
NSLog(@"name-------%@",person.name);
}
- (void)viewDidLoad {
[super viewDidLoad];
[self archiverAndSaveSandBox];
[self unArichiver];
}
三.數(shù)據(jù)庫存儲
1.這里必須先要導(dǎo)入一個系統(tǒng)文件。在xcode7.0之后把后綴改了掂骏,現(xiàn)在叫l(wèi)ibsqlite3.tbd
//里面有操作sqlite數(shù)據(jù)庫的所有函數(shù)轰驳,我們要操作數(shù)據(jù)庫,就需要導(dǎo)入libSqlite3的系統(tǒng)庫 路徑:TARGETS -> Build Phases -> Link Binary with Libraries
導(dǎo)入頭文件
#import <sqlite3.h> //里面有操作sqlite數(shù)據(jù)庫的所有函數(shù)弟灼,我們要操作數(shù)據(jù)庫级解,就需要導(dǎo)入libSqlite3的系統(tǒng)庫 路徑:TARGETS -> Build Phases -> Link Binary with Libraries
#import "SandBoxPaths.h"
@implementation RootViewController
#pragma mark ------ 打開或者創(chuàng)建數(shù)據(jù)庫
// 打開或者創(chuàng)建數(shù)據(jù)庫
- (sqlite3 *)openOrCreateDB{
// 首先我們需要一個保存數(shù)據(jù)庫的文件路徑
NSString *dbPath = [[SandBoxPaths documentsPath]stringByAppendingPathComponent:@"db.sqlite" ];
// 數(shù)據(jù)庫的句柄
sqlite3 *sqlite3 = NULL;
// 如果數(shù)據(jù)庫已經(jīng)存在,此函數(shù)就是打開當(dāng)前數(shù)據(jù)庫文件田绑,如果該數(shù)據(jù)庫文件不存在勤哗,那么此函數(shù)就是創(chuàng)建數(shù)據(jù)庫文件并打開。
// filename:數(shù)據(jù)庫文件的路徑
// ppDb: 數(shù)據(jù)庫句柄此變量的指針的指針掩驱。當(dāng)前數(shù)據(jù)庫創(chuàng)建或者打開成功之后芒划,會將地址指針保存在該參數(shù)中冬竟,這樣,此句柄變量就可以通過指針來操作數(shù)據(jù)庫民逼。
// 由于此函數(shù)為C函數(shù),但是dbPath為OC對象泵殴,所以需要將OC字符串轉(zhuǎn)換為C字符串
// 此函數(shù)有返回值,sqlite3中對所有的操作拼苍,只要沒有返回?cái)?shù)據(jù)結(jié)果集的笑诅,都會有一個int的返回值,來標(biāo)識此操作是否進(jìn)行成功疮鲫。
int result = sqlite3_open(dbPath.UTF8String, &sqlite3);
if (result == SQLITE_OK) {
NSLog(@"數(shù)據(jù)庫打開成功");
return sqlite3;
}else{
NSLog(@"數(shù)據(jù)庫打開失敗");
return NULL;
}
}
#pragma mark ---------- 執(zhí)行無返回結(jié)果集的SQL操作
// 執(zhí)行無返回結(jié)果集的SQL操作
- (BOOL)exeSqlWithSQLString:(NSString *)sqlStr{
// 打開數(shù)據(jù)庫
sqlite3 *sqlDB = [self openOrCreateDB];
// 執(zhí)行SQL的函數(shù)
// 第一個參數(shù):數(shù)據(jù)庫的句柄吆你,可以理解為就是數(shù)據(jù)庫
// 第二個參數(shù):所要執(zhí)行的sql語句
// 第三個參數(shù):執(zhí)行完SQL之后的回調(diào)方法。
// 第四個參數(shù):回調(diào)方法的第一個參數(shù)
// 第五個參數(shù):錯誤日志棚点,等同于OC中的NSError,這里是char類型湾蔓。
int result = sqlite3_exec(sqlDB, sqlStr.UTF8String, NULL, NULL, NULL);
if (result == SQLITE_OK) {
NSLog(@"語句執(zhí)行成功");
// 關(guān)閉數(shù)據(jù)庫
sqlite3_close(sqlDB);
return YES;
}else{
NSLog(@"語句執(zhí)行失敗");
// 關(guān)閉數(shù)據(jù)庫
sqlite3_close(sqlDB);
return NO;
}
}
#pragma mark ----- 查詢數(shù)據(jù)庫
// 查詢數(shù)據(jù)庫
- (NSArray *)queryDBWithSqlString:(NSString *)sqlStr{
// 初始化一個可變數(shù)組瘫析,一會用來盛放所有的結(jié)果
NSMutableArray *resultMutableArray = [[NSMutableArray alloc]init];
// 打開數(shù)據(jù)庫
sqlite3 *sqlDB = [self openOrCreateDB];
// 用來保存記錄的指針對象
sqlite3_stmt *stament = NULL;
// 用來檢查SQL的函數(shù),如果SQL語句編譯無問題默责,就將編譯好的SQL保存到stament中
int result = sqlite3_prepare(sqlDB, sqlStr.UTF8String, -1, &stament, NULL);
if (result == SQLITE_OK) {
while (sqlite3_step(stament) == SQLITE_ROW){
// while每執(zhí)行一次贬循,就會有一條新記錄,所以我們每次都需要一個新的字典桃序,也就是初始化好的字典來盛放記錄
NSMutableDictionary *rowDic = [NSMutableDictionary dictionary];
// 每執(zhí)行一次step函數(shù)杖虾,都會在stament中保存一條完整的記錄。
// 取出一條記錄中的某個字段
// 第二個參數(shù)是指取出第幾列的字段值
int number = sqlite3_column_int(stament, 0);
[rowDic setObject:[NSNumber numberWithInt:number]forKey:@"number"];
const unsigned char *name = sqlite3_column_text(stament, 1);
NSString *nameString = [NSString stringWithCString:(const char *)name encoding:NSUTF8StringEncoding];
// NSString *nameString = [NSString stringWithUTF8String:(const char *)name];
[rowDic setObject:nameString forKey:@"name"];
const unsigned char *gender = sqlite3_column_text(stament, 2);
NSString *genderString = [NSString stringWithCString:(const char *)gender encoding:NSUTF8StringEncoding];
[rowDic setObject:genderString forKey:@"gender"];
//組裝好字典之后媒熊,將該字典放入數(shù)組
[resultMutableArray addObject:rowDic];
}
}
// 關(guān)閉數(shù)據(jù)庫
sqlite3_close(sqlDB);
// 釋放stament所持有的資源
sqlite3_finalize(stament);
return resultMutableArray;
}
這里封裝了DataBaseHelper單例可以直接拿來使用奇适。
四。Core Data存儲機(jī)制
大致瀏覽下基本感覺就是將對象歸檔搞成了可視化和簡單化芦鳍。
這塊內(nèi)容比較多嚷往。網(wǎng)上資料也挺豐富的。
暫時(shí)不做介紹了柠衅。
總結(jié)下:其實(shí)對于ios數(shù)據(jù)存儲皮仁,最常用和主要要掌握的就是屬性列表和數(shù)據(jù)庫,因?yàn)閮蓚€是出鏡率比較高的菲宴。
其他可能在數(shù)據(jù)存明顯體現(xiàn)出儲優(yōu)勢時(shí)贷祈,我們會去考慮用另外兩種機(jī)制。
基礎(chǔ)的來說喝峦,必須掌握屬性列表和sqlite的操作存儲势誊。