iOS應(yīng)用程序中存儲(chǔ)數(shù)據(jù)的幾種方式
在iOS系統(tǒng)中對(duì)數(shù)據(jù)做持久化存儲(chǔ)的方式一般有5中方式:
- 文件寫入
- 對(duì)象歸檔
- SQLite數(shù)據(jù)庫
- CoreData
-
NSUserDefaults
閃存和沙盒
在iPhone/iPad設(shè)備上包含閃存兼搏,它的功能和一個(gè)硬盤的功能等價(jià)症虑。當(dāng)設(shè)備斷電后數(shù)據(jù)還能被保存下來膨俐。應(yīng)用程序可以將文件保存下來勇皇,并能從閃存中讀取它們。但是我們的應(yīng)用程序不能訪問整個(gè)閃存焚刺,閃存上上面的一部分專門給你的應(yīng)用程序敛摘,這就是你應(yīng)用程序的沙盒(sandbox)。每個(gè)應(yīng)用程序只能看到自己的沙盒乳愉,這就防止了對(duì)其他應(yīng)用程序的文件進(jìn)行讀取的活動(dòng)兄淫。
用戶默認(rèn)設(shè)置 - NSUserDefaults
Apple將整個(gè)首選項(xiàng)系稱為應(yīng)用程序首選項(xiàng)屯远,用戶可以通過它定制應(yīng)用程序。應(yīng)用程序首選項(xiàng)系統(tǒng)負(fù)責(zé)如下任務(wù):
- 將首選項(xiàng)持久化到設(shè)備中
- 將各個(gè)應(yīng)用程序的首選項(xiàng)彼此分開
- 通過iTune將應(yīng)用程序首選項(xiàng)備份到計(jì)算機(jī)捕虽,以免在需要恢復(fù)設(shè)備時(shí)用戶丟失首選項(xiàng)慨丐。
我們可以通過一個(gè)易于使用的API與應(yīng)用程序首選項(xiàng)交互,該API主要由單例類NSUserDefaults組成泄私。
類NSUserDefaults的工作原理類似于NSDictionary,主要差別在于NSUserDefaults是單例類房揭,且在它可以存儲(chǔ)的對(duì)象類型方面受到很多的限制。應(yīng)用程序的所有首選項(xiàng)都是以“鍵-值”對(duì)的方式存儲(chǔ)在NSUserDefaults單例類中晌端。
訪問應(yīng)用程序首選項(xiàng)
訪問應(yīng)用程序首選項(xiàng)必須獲取指向應(yīng)用程序的NSUserDefaults單例的引用:
NSUserDefaults * userDefault = [NSUserDefaults standardUserDefaults];
然后就可以讀寫默認(rèn)設(shè)置數(shù)據(jù)庫了捅暴。提供了以下方法寫入數(shù)據(jù)以及訪問數(shù)據(jù):
- (void)setObject:(nullable id)value forKey:(NSString *)defaultName;
- (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName;
- (void)setFloat:(float)value forKey:(NSString *)defaultName;
- (void)setDouble:(double)value forKey:(NSString *)defaultName;
- (void)setBool:(BOOL)value forKey:(NSString *)defaultName;
- (void)setURL:(nullable NSURL *)url forKey:(NSString *)defaultName NS_AVAILABLE(10_6, 4_0);
具體使用哪一個(gè)函數(shù)取決于要存儲(chǔ)的數(shù)據(jù)類型。函數(shù)setObject:forKey可以存儲(chǔ)NSString咧纠、NSData蓬痒、NSArray以及其他常見的對(duì)象類型。例如使用鍵age存儲(chǔ)一個(gè)整數(shù)20漆羔,用鍵oc存儲(chǔ)一個(gè)字符串HelloWorld:
//存儲(chǔ)一個(gè)整數(shù)
[userDefault setInteger:20 forKey:@"age"];
//存儲(chǔ)一個(gè)字符串
[userDefault setObject:@"HelloWorld" forKey:@"oc"];
但是當(dāng)我們將數(shù)據(jù)寫入默認(rèn)設(shè)置數(shù)據(jù)庫時(shí)梧奢,并不一定會(huì)立即保存這些數(shù)據(jù)。為了確保所有數(shù)據(jù)都寫入了用戶默認(rèn)設(shè)置钧椰,可以使用synchronize方法:
[userDefault synchronize];
示例代碼
NSUserDefaults * userDefault = [NSUserDefaults standardUserDefaults];
//存儲(chǔ)一個(gè)整數(shù)
[userDefault setInteger:20 forKey:@"age"];
//存儲(chǔ)一個(gè)字符串
[userDefault setObject:@"HelloWorld" forKey:@"oc"];
// 馬上存儲(chǔ)
[userDefault synchronize];
NSInteger integer =[[NSUserDefaults standardUserDefaults] integerForKey:@"age"];
NSString * string = [[NSUserDefaults standardUserDefaults] stringForKey:@"oc"];
NSLog(@"integer-%ld",integer);
NSLog(@"string-%@", string);
打印結(jié)果
對(duì)NSUserDefaults的簡(jiǎn)單封裝
雖然該通過這樣的方實(shí)現(xiàn)了對(duì)數(shù)據(jù)用鍵值對(duì)的方式進(jìn)行存取粹断,但是每次存取都要寫一大堆的相同的代碼。為了實(shí)現(xiàn)封裝嫡霞,可以新建一個(gè)類,在這個(gè)類實(shí)現(xiàn)對(duì)NSUserDefaults存儲(chǔ)希柿,讀取和刪除:
#import "LMHUserDefault.h"
@implementation LMHUserDefault
// 存儲(chǔ)用戶偏好設(shè)置到NSUserDefults
+ (void)setUserData:(id)data forKey:(NSString*)key
{
if (data == nil)
{
return;
}
else
{
[[NSUserDefaults standardUserDefaults] setObject:data forKey:key];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
//讀取用戶偏好設(shè)置
+ (id)readUserDataWithKey:(NSString*)key
{
id temp = [[NSUserDefaults standardUserDefaults] objectForKey:key];
if(temp != nil)
{
return temp;
}
return nil;
}
//刪除用戶偏好設(shè)置
+ (void)removeUserDataWithkey:(NSString*)key
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
}
那么在要用到的地方只需要將這個(gè)類的頭文文件導(dǎo)入诊沪,然后調(diào)用相應(yīng)的類方法即可。以上的代碼就可以用下面的方式實(shí)現(xiàn):
- (void)viewDidLoad {
[super viewDidLoad];
//存儲(chǔ)一個(gè)整數(shù)
[LMHUserDefault setUserData:@(20) forKey:@"age"];
//存儲(chǔ)一個(gè)字符串
[LMHUserDefault setUserData:@("HelloWorld") forKey:@"oc"];
// 讀取
NSInteger integer = (NSInteger)[LMHUserDefault readUserDataWithKey:@"age"];
NSString * string = (NSString *)[LMHUserDefault readUserDataWithKey:@"oc"];
// 打印
NSLog(@"age----%ld", integer);
NSLog(@"oc-----%@", string);
}
打印結(jié)果:
直接訪問文件系統(tǒng)
通過NSUserDefaults存儲(chǔ)的對(duì)象有一些限制曾撤,但是銅鼓直接范文文件系統(tǒng)的方式可以存儲(chǔ)任何類型的數(shù)據(jù)端姚。
直接訪問文件系統(tǒng)是指打開文件并讀寫其內(nèi)容。例如從Internet下載的文件挤悉、應(yīng)用程序創(chuàng)建的文件扥渐裸,但并非能存儲(chǔ)到任何地方。因?yàn)閕OS中有沙盒的限制装悲。
應(yīng)用程序目錄中有四個(gè)位置專門用來存取應(yīng)用程序的數(shù)據(jù):
-
Library/Caches
- 用戶緩存從網(wǎng)絡(luò)獲取的數(shù)據(jù)昏鹃、通過大量計(jì)算得到的數(shù)據(jù)。
- 該目錄存放的數(shù)據(jù)將在應(yīng)用程序關(guān)閉時(shí)得以保留诀诊,所以** 如果希望下載的文件永久有效(除非用戶卸載軟件)洞渤,那么可以放在這里。**
Library/Preference
-
Documents
- 應(yīng)用程序數(shù)據(jù)的主要存儲(chǔ)位置
- 設(shè)備與iTunes同步的時(shí)候属瓣,該目錄將備份到計(jì)算機(jī)中载迄,因此不能將大文件放在這里讯柔,備份起來很麻煩
- 另外,審核的時(shí)候如果發(fā)現(xiàn)將下載的東西放在這里护昧,審核不會(huì)通過
-
tmp
- 應(yīng)用程序儲(chǔ)存臨時(shí)文件夾魂迄。
- 由于是臨時(shí)文件,所以不靠譜惋耙,里面的東西隨時(shí)都可能被清理掉极祸。
獲取文件路徑
每個(gè)文件都有路徑,這個(gè)路徑是文件在文件系統(tǒng)中的準(zhǔn)確位置怠晴。要讓應(yīng)用程序能夠讀寫其沙盒中的文件遥金,需要指定該文件的完整路徑。在Core Foundation中提供了一個(gè)名為NSS eachPathForDirectoriesInDomains的C語言函數(shù)蒜田,它返回的是指向應(yīng)用程序的目錄Documents或Library/Caches路徑稿械。由于這個(gè)函數(shù)可以返回多個(gè)目錄,因此該函數(shù)調(diào)用的結(jié)果是一個(gè)NSArray對(duì)象冲粤。使用該函數(shù)來獲取指向目錄Documents或Library/Caches路徑時(shí)美莫,它返回的數(shù)組將只包含一個(gè)NSString;要從數(shù)組中獲取該路徑的NSString梯捕,可以將數(shù)組的索引設(shè)置為0.
獲取Library/Caches的文件路徑
// 獲取文件路徑
NSString * cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
另外NSString的stringByAppendingPathComponent可以將兩個(gè)路徑拼接起來厢呵。
獲取Documents的文件路徑
如果要獲取Documents中的特定文件路徑,只需要將NSCachesDirectory換成NSDocumentDirectory即可:
// 獲取Documents目錄中的文件路徑
NSString * docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
獲取tmp的文件路徑
獲取tmp的文件路徑可以通過Core Foundation中的另一套C語言函數(shù)NSTemporaryDirectory
// 獲取tmp的文件路徑
NSString * tmpFile = NSTemporaryDirectory();
讀取數(shù)據(jù)
要讀寫文件中的數(shù)據(jù)傀顾,首先要檢查文件是否存在襟铭,如果不存在則需要的話需要先創(chuàng)建它。
// 檢查myPath的文件是否存在
if ([[NSFileManager defaultManager] fileExistsAtPath:myPath]) {
// 存在
}else{
// 不存在
}
然后可以使用類NSFileHandle提供的方法獲取文件的引用短曾,再進(jìn)行讀取和寫入數(shù)據(jù)寒砖,NSFileHandle中提供的獲取文件引用的方法:
+ (nullable instancetype)fileHandleForReadingAtPath:(NSString *)path;
+ (nullable instancetype)fileHandleForWritingAtPath:(NSString *)path;
+ (nullable instancetype)fileHandleForUpdatingAtPath:(NSString *)path;
+ (nullable instancetype)fileHandleForReadingFromURL:(NSURL *)url error:(NSError **)error NS_AVAILABLE(10_6, 4_0);
+ (nullable instancetype)fileHandleForWritingToURL:(NSURL *)url error:(NSError **)error NS_AVAILABLE(10_6, 4_0);
+ (nullable instancetype)fileHandleForUpdatingURL:(NSURL *)url error:(NSError **)error NS_AVAILABLE(10_6, 4_0);
如果要寫入數(shù)據(jù),可以使用NSFileHandle中的writeData方法.
在最后還要關(guān)閉手柄嫉拐。所以總體而言哩都,分為四個(gè)步驟:
- 檢查指定文件是否存在
- 獲取該文件的引用
- 讀取文件/寫入文件
- 關(guān)閉手柄
// 檢查myPath的文件是否存在
if ([[NSFileManager defaultManager] fileExistsAtPath:myPath]) {
// 存在
}else{
// 不存在
}
// 獲取文件引用
NSFileHandle * fileHandle = [NSFileHandle fileHandleForWritingAtPath:myPath];
// 寫入數(shù)據(jù)
[fileHandle writeData:stringData];
// 關(guān)閉手柄
[fileHandle closeFile];
使用SQlite3存儲(chǔ)和讀取數(shù)據(jù)
SQLite3是嵌入在IOS中的關(guān)系型數(shù)據(jù)庫,對(duì)于存儲(chǔ)大型的數(shù)據(jù)很有效婉徘。SQLite3不必將每個(gè)對(duì)象都加載到內(nèi)存中漠嵌。在iOS中,對(duì)SQLite3有如下基本操作:
- 打開或者創(chuàng)建數(shù)據(jù)庫
// 打開或創(chuàng)建數(shù)據(jù)庫
sqlite3 * database;
if (sqlite3_open([self.databaseFilePath UTF8String], &database) != SQLITE_OK) {
sqlite3_close(database);
NSAssert(0, @"打開數(shù)據(jù)庫失敗盖呼!");
}
- 關(guān)閉數(shù)據(jù)庫
// 關(guān)閉數(shù)據(jù)庫
sqlite3_close(database);
- 創(chuàng)建一個(gè)表格
// 創(chuàng)建數(shù)據(jù)庫表
NSString * createSQL = @"CREATE TABLE IF NOT EXISTS FIELDS (TAG INTEGER PRIMARY KEY, FIELD_DATA TEXT);";
char * errorMsg;
if (sqlite3_exec(database, [createSQL UTF8String], NULL, NULL, &errorMsg) != SQLITE_OK) {
sqlite3_close(database);
NSAssert(0, @"創(chuàng)建數(shù)據(jù)庫表格錯(cuò)誤:%s", errorMsg);
}
- 查詢操作
// 執(zhí)行查詢
NSString * query = @"SELECT TAG, FIELD_DATA FROM FIELDS ORDER BY TAG";
sqlite3_stmt * statement;
if (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) == SQLITE_OK) {
// 依次讀取數(shù)據(jù)數(shù)據(jù)庫表格FIELDS中每行的內(nèi)容儒鹿,并顯示在對(duì)應(yīng)的textFiled上
while (sqlite3_step(statement) == SQLITE_ROW) {
// 獲取數(shù)據(jù)
int tag = sqlite3_column_int(statement, 0);
char * rowData = (char *)sqlite3_column_text(statement, 1);
// 根據(jù)tag獲得對(duì)應(yīng)的textField
UITextField * textfield = (UITextField *)[self.view viewWithTag:tag];
// 設(shè)置文本
textfield.text = [[NSString alloc] initWithUTF8String:rowData];
}
sqlite3_finalize(statement);
}
- 使用約束變量
實(shí)際操作中經(jīng)常使用叫做約束變量的東西來構(gòu)造SQL字符串,從而進(jìn)行增刪改查的操作塌计。
// 向表格插入4行數(shù)據(jù)
for (int i = 0; i < 4; i++) {
//根據(jù)tag獲取textfiel
UITextField * textfield = (UITextField *)[self.view viewWithTag:i];
// 使用約束變量插入數(shù)據(jù)
char * updata = "INSERT OR REPLACE INTO FIELDS(TAG, FIELDS_DATA) VALUES(?, ?);";
sqlite3_stmt * stmt;
if (sqlite3_prepare_v2(database, updata, -1, &stmt, nil) == SQLITE_OK) {
sqlite3_bind_int(stmt, 1, i);
sqlite3_bind_text(stmt, 2, [textfield.text UTF8String], -1, NULL);
}
char * errorMsg = NULL;
if (sqlite3_step(stmt) != SQLITE_OK) {
NSAssert(0, @"更新數(shù)據(jù)庫表FIELDS出錯(cuò):%s",errorMsg);
}
sqlite3_finalize(stmt);
}