ios - 讀寫應(yīng)用程序的幾種方式之NSUserDefaults匀伏、文件寫入荒辕、SQLite數(shù)據(jù)庫

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);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挺身,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子锌仅,更是在濱河造成了極大的恐慌章钾,老刑警劉巖墙贱,帶你破解...
    沈念sama閱讀 212,222評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異贱傀,居然都是意外死亡惨撇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門府寒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來魁衙,“玉大人,你說我怎么就攤上這事株搔∑实恚” “怎么了?”我有些...
    開封第一講書人閱讀 157,720評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵纤房,是天一觀的道長(zhǎng)纵隔。 經(jīng)常有香客問我,道長(zhǎng)炮姨,這世上最難降的妖魔是什么捌刮? 我笑而不...
    開封第一講書人閱讀 56,568評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮舒岸,結(jié)果婚禮上绅作,老公的妹妹穿的比我還像新娘。我一直安慰自己蛾派,他們只是感情好俄认,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著碍脏,像睡著了一般梭依。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上典尾,一...
    開封第一講書人閱讀 49,879評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音糊探,去河邊找鬼钾埂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛科平,可吹牛的內(nèi)容都是我干的褥紫。 我是一名探鬼主播,決...
    沈念sama閱讀 39,028評(píng)論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼瞪慧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼髓考!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起弃酌,我...
    開封第一講書人閱讀 37,773評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤氨菇,失蹤者是張志新(化名)和其女友劉穎儡炼,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體查蓉,經(jīng)...
    沈念sama閱讀 44,220評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乌询,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了豌研。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妹田。...
    茶點(diǎn)故事閱讀 38,697評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鹃共,靈堂內(nèi)的尸體忽然破棺而出鬼佣,到底是詐尸還是另有隱情,我是刑警寧澤霜浴,帶...
    沈念sama閱讀 34,360評(píng)論 4 332
  • 正文 年R本政府宣布晶衷,位于F島的核電站,受9級(jí)特大地震影響坷随,放射性物質(zhì)發(fā)生泄漏房铭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評(píng)論 3 315
  • 文/蒙蒙 一温眉、第九天 我趴在偏房一處隱蔽的房頂上張望缸匪。 院中可真熱鬧,春花似錦类溢、人聲如沸凌蔬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽砂心。三九已至,卻和暖如春蛇耀,著一層夾襖步出監(jiān)牢的瞬間辩诞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評(píng)論 1 266
  • 我被黑心中介騙來泰國打工纺涤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留译暂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,433評(píng)論 2 360
  • 正文 我出身青樓撩炊,卻偏偏與公主長(zhǎng)得像外永,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拧咳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評(píng)論 2 350

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