IOS數(shù)據(jù)存儲方式匯總

@TOC

IOS數(shù)據(jù)存儲簡介

  • 在項目開發(fā)當(dāng)中,我們經(jīng)常會對一些數(shù)據(jù)進行本地緩存處理岛都。離線緩存的數(shù)據(jù)一般都保存在APP所在的沙盒之中淑履。一般有以下幾種:
存儲方式 優(yōu)點 缺點 適用場景 備注
PList(XML屬性列表) 快速效率高 不靈活钱贯,只能存儲常用的類型 只適用于系統(tǒng)自帶的一些常用類型才能用 備注
偏好設(shè)置(NSUserDefaults) 快速效率高窗宇,簡單易用 只能存儲常用的類型 適用場景 備注
歸檔(NSCoding NSKeyedArchiver NSKeyedUnarchiver) 歸檔可以實現(xiàn)把自定義的對象存放在文件中 必須實現(xiàn)協(xié)議措伐,浸入性強 適用場景 備注
SQLITE數(shù)據(jù)庫 靈活 操作復(fù)雜 適用場景 備注
FMDB 是對sqlite的封裝,簡單易用军俊,接口比原生的SQLite接口簡潔很多侥加,提供一些多線程,緩存粪躬,線程池的功能 缺點 適用場景 備注
CoreData 蘋果自帶的數(shù)據(jù)存儲方式 缺點 適用場景 備注
WCDB 騰訊開發(fā)的一款DB工具 缺點 適用場景 備注

IOS 沙盒存儲路徑

  • 要存儲數(shù)據(jù)担败,必須要了解蘋果的沙盒存儲機制,沙盒相關(guān)路徑的規(guī)則
沙盒目錄 路徑說明 備注
Documents iTunes會備份該目錄镰官。一般用來存儲需要持久化的數(shù)據(jù)提前。
Library/Caches 緩存,iTunes不會備份該目錄泳唠。內(nèi)存不足時會被清除狈网,應(yīng)用沒有運行時,可能會被清除笨腥,拓哺。一般存儲體積大、不需要備份的非重要數(shù)據(jù)脖母。
Library/Preference iTunes同會備份該目錄士鸥,可以用來存儲一些偏好設(shè)置。
tmp iTunes不會備份這個目錄谆级,用來保存臨時數(shù)據(jù)烤礁,應(yīng)用退出時會清除該目錄下的數(shù)據(jù)。
  • 獲取沙盒路徑
// 獲取Documents目錄的路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
// 得到Document目錄下的fileName文件的路徑
NSString *filePath = [documentPath stringByAppendingPathComponent:fileName];

//獲取Library/Caches目錄路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
//獲取Library/Caches目錄下的fileName文件路徑
NSString *filePath = [path stringByAppendingPathComponent:fileName];

//獲取temp路徑
NSString *tmp = NSTemporaryDirectory();
//獲取temp下fileName文件的路徑
NSString *filePath = [tmp stringByAppendingPathComponent:fileName];

IOS數(shù)據(jù)存儲方式

1. PList(XML屬性列表)

  • 在使用plist進行數(shù)據(jù)存儲和讀取肥照,只適用于系統(tǒng)自帶的一些常用類型才能用脚仔,且必須先獲取路徑相對麻煩
  • 可以把字典或數(shù)組直接寫入到文件中。另外舆绎,NSString玻侥、NSData、NSNumber等類型亿蒸,也可以使用writeToFile:atomically:方法直接將對象寫入文件中凑兰,只是Type為空。
  • 實例1

oc代碼

//寫入文件
NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [doc stringByAppendingPathComponent:@"myself.plist"];
NSDictionary *dict = @{@"name": @"yixiang"};
[dict writeToFile:path atomically:YES];
//讀取文件
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];

2. 偏好設(shè)置(NSUserDefaults)

  • 將所有的東西都保存在同一個文件夾下面边锁,且主要用于存儲應(yīng)用的設(shè)置信息)
  • NSUserDefaults可以存儲的數(shù)據(jù)類型包括:NSData姑食、NSString、NSNumber茅坛、NSDate音半、NSArray则拷、NSDictionary。如果要存儲其他類型曹鸠,則需要轉(zhuǎn)換為前面的類型煌茬,才能用NSUserDefaults存儲。
  • 實例2
    OC代碼
//寫入文件
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:@"yixiang" forKey:@"name"];
[defaults setInteger:27 forKey:@"age"];
[defaults synchronize];
//讀取文件
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
NSString *name=[defaults objectForKey:@"name"];
NSInteger age=[defaults integerForKey:@"age"];

3. 歸檔(NSCoding NSKeyedArchiver NSKeyedUnarchiver)

  • 因為PList存儲和NSUserDefaults存儲都有一個致命的缺陷彻桃,只能存儲常用的類型坛善。歸檔可以實現(xiàn)把自定義的對象存放在文件中。
  • 需要保存的對象必須遵守NSCoding協(xié)議邻眷,并且實現(xiàn)該協(xié)議中- (void)encodeWithCoder:(NSCoder )aCoder和 - (id)initWithCoder:(NSCoder )aDecoder方法眠屎。
  • 實例3
    OC代碼
//YXPerson.h文件如下:

@interface YXPerson : NSObject<NSCoding>
  @property(nonatomic,copy) NSString *name;
  @property(nonatomic,assign) int age;
  @end
//YXPerson.m文件如下:

 #import "YYPerson.h"

  @implementation YYPerson
  -(void)encodeWithCoder:(NSCoder *)aCoder{
      [aCoder encodeObject:self.name forKey:@"name"];
      [aCoder encodeInteger:self.age forKey:@"age"];
  }
  -(id)initWithCoder:(NSCoder *)aDecoder{
      if (self=[super init]) {
          self.name=[aDecoder decodeObjectForKey:@"name"];
          self.age=[aDecoder decodeIntegerForKey:@"age"];
      }
      return self;
  }
  @end

在ViewController中對它進行寫入和讀取

 //寫入對象
    YXPerson *p=[[YXPerson alloc]init];
    p.name=@"yixiang";
    p.age=27;

    NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
    NSString *path=[docPath stringByAppendingPathComponent:@"person.yixiang"];

    [NSKeyedArchiver archiveRootObject:p toFile:path];

     //讀取對象
     YXPerson *p=[NSKeyedUnarchiver unarchiveObjectWithFile:path];

4. SQLITE數(shù)據(jù)庫

  • 上述三種方法都無法存儲大批量的數(shù)據(jù),有性能的問題肆饶。

  • SQLITE作為程序員都比較熟悉改衩,它是跨平臺的數(shù)據(jù)庫工具。下面簡單介紹一下驯镊,如何打開數(shù)據(jù)庫葫督,新增一張表格,然后對其進行增刪改查的操作板惑。

  • 詳細(xì)SQLite相關(guān)性能優(yōu)化可以參考這篇文章:微信iOS SQLite源碼優(yōu)化實踐

  • 實例4
    OC代碼

- (void)openDB{
    //獲取數(shù)據(jù)庫文件路徑
    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *fileName = [doc stringByAppendingPathComponent:@"students.sqlite"];

    //將OC字符串轉(zhuǎn)換為c語言的字符串
    const char *cfileName = fileName.UTF8String;

    //打開數(shù)據(jù)庫文件(如果數(shù)據(jù)庫文件不存在候衍,那么該函數(shù)會自動創(chuàng)建數(shù)據(jù)庫文件)
    int result = sqlite3_open(cfileName, &_db);
    if (result == SQLITE_OK) {//打開成功
        NSLog(@"成功打開數(shù)據(jù)庫");
    }else{
        NSLog(@"打開數(shù)據(jù)庫失敗");
    }
}


- (void)createTable{
    //創(chuàng)建表
    const char *sql = "CREATE TABLE IF NOT EXISTS t_student(id integer PRIMARY KEY AUTOINCREMENT,name text NOT NULL,age integer NOT NULL);";
    char *errmsg= NULL;
    int result = sqlite3_exec(_db, sql, NULL, NULL, &errmsg);
    if (result==SQLITE_OK) {
        NSLog(@"創(chuàng)建表成功");
    }else{
        NSLog(@"創(chuàng)建表失敗---%s",errmsg);
    }
}


- (void)insertData{
    //插入數(shù)據(jù)
    for (int i=0; i<10; i++) {
        //拼接sql語句
        NSString *name = [NSString stringWithFormat:@"yixiangboy--%d",arc4random_uniform(100)];
        int age = arc4random_uniform(20)+10;
        NSString *sql = [NSString stringWithFormat:@"INSERT INTO t_student (name,age) VALUES ('%@',%d);",name,age];

         //執(zhí)行SQL語句
        char *errmsg = NULL;
        sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errmsg);
        if (errmsg) {//如果有錯誤信息
            NSLog(@"插入數(shù)據(jù)失敗--%s",errmsg);
        }else{
            NSLog(@"插入數(shù)據(jù)成功");
        }
    }
}


- (void)deleteData{
    //刪除age小于15的數(shù)據(jù)
    NSString *sql = [NSString stringWithFormat:@"DELETE FROM t_student WHERE age<15"];
    char *errmsg = NULL;
    sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errmsg);
    if (errmsg) {
        NSLog(@"刪除數(shù)據(jù)失敗");
    }else{
        NSLog(@"刪除數(shù)據(jù)成功");
    }
}

- (void)updateData{
    //大于20歲的都置為20歲
    NSString *sql = [NSString stringWithFormat:@"UPDATE t_student set age=20 WHERE age>20"];
    char *errmsg = NULL;
    sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errmsg);
    if (errmsg) {
        NSLog(@"更新數(shù)據(jù)失敗");
    }else{
        NSLog(@"更新數(shù)據(jù)成功");
    }
}


- (void)queryData{
    const char *sql = "SELECT id,name,age FROM t_student WHERE age<20";
    sqlite3_stmt *stmt = NULL;

    //進行查詢前的準(zhǔn)備工作
    if(sqlite3_prepare_v2(_db, sql, -1, &stmt, NULL)==SQLITE_OK){//SQL語句沒有問題
        NSLog(@"查詢語句沒有問題");

        //每調(diào)用一次sqlite3_step函數(shù),stmt就會指向下一條記錄
        while (sqlite3_step(stmt)==SQLITE_ROW) {//找到一條記錄
            //取出數(shù)據(jù)
            //(1)取出第0個字段的值(int)
            int ID=sqlite3_column_int(stmt, 0);
            //(2)取出第一列字段的值(text)
            const unsigned char *name = sqlite3_column_text(stmt, 1);
            //(3)取出第二列字段的值(int)
            int age = sqlite3_column_int(stmt, 2);

            printf("%d %s %d\n",ID,name,age);
        }
    }else{
        NSLog(@"查詢語句有問題");
    }
}

5. FMDB

5.1 FMDB 簡介

  • FMDB 做IOS的也應(yīng)該都耳熟能詳了洒放,項目中都用過。是一個很牛逼的DB工具滨砍。
  • FMDB 是對SQLIite數(shù)據(jù)庫的C語言接口進行了一層封裝往湿,使其滿足面向?qū)ο蟮牟僮鳎涌诒仍腟QLite接口簡潔很多惋戏。同時也提供一些多線程领追,緩存,線程池的功能响逢。
  • FMDB的三個類:
功能 備注
FMDatabase 可以理解成一個數(shù)據(jù)庫 備注
FMResultSet 查詢的結(jié)果集合 備注
FMDatabaseQueue 運用多線程绒窑,可執(zhí)行多個查詢、更新舔亭,線程安全 備注

5.2 FMDB 創(chuàng)建數(shù)據(jù)庫

  • FMDatabase創(chuàng)建的時候需要提供一個SQLite數(shù)據(jù)庫文件些膨。我們一般提供一個.db的文件路徑即可。
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];

5.3 FMDB 打開數(shù)據(jù)庫钦铺,關(guān)閉數(shù)據(jù)庫

  • 打開數(shù)據(jù)庫:在和數(shù)據(jù)庫進行交互之前订雾,我們需要先打開數(shù)據(jù)庫。使用上一步拿到的數(shù)據(jù)庫文件操作句柄db
if (![db open]) 
{
    db = nil;
    return;
}
  • 關(guān)閉數(shù)據(jù)庫
[db close];

5.4 FMDB 創(chuàng)建表

  • 使用集合語句來進行表的創(chuàng)建矛洞。
NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
                 "create table bulktest2 (id integer primary key autoincrement, y text);"
                 "create table bulktest3 (id integer primary key autoincrement, z text);"
                 "insert into bulktest1 (x) values ('XXX');"
                 "insert into bulktest2 (y) values ('YYY');"
                 "insert into bulktest3 (z) values ('ZZZ');";

success = [db executeStatements:sql];

5.5 FMDB 增洼哎,刪,改,查

5.5.1 查詢

  • 查詢:使用executeQuery 來執(zhí)行查詢語句噩峦,F(xiàn)MResultSet來進行存儲查詢的結(jié)果锭沟。
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
    //retrieve values for each record
}

//可通過如下的方式將結(jié)果集里面的數(shù)據(jù)取出來。

while ([rs next])
{
    if (oldIDs == nil) oldIDs = [[NSMutableArray alloc] init];
    [oldIDs addObject:[rs stringForColumnIndex:0]];
}

另外识补,F(xiàn)MResultSet可以通過如下的方法將數(shù)據(jù)通過恰當(dāng)?shù)母袷綑z索出來族淮。

intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumn:
objectForColumn:

5.5.2 更新

  • 通過調(diào)用- (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments執(zhí)行插入、刪除或者更新數(shù)據(jù)李请。
    插入數(shù)據(jù)或者更新數(shù)據(jù)瞧筛。
NSString *sql = ![rs next] ? INSERT_STATEMENT : UPDATE_STATEMENT;
BOOL success = [db executeUpdate:sql withParameterDictionary:[self pr_toDBDictionary]];
if (!success)
{
    STLogDBLastError(db);
    result = NO;
    return;
}

5.5.3 刪除

  • 通過執(zhí)行sql語句刪除
BOOL success = [db executeUpdate:@"DELETE FROM STAppLog WHERE userID = ? AND appLogID = ?;", self.userID, self.appLogID];
if (!success)
{
    STLogDBLastError(db);
    result = NO;
    return;
}

5.5.4 新增

5.6 FMDB 多線程操作數(shù)據(jù)庫

  • FMDatabaseQueue內(nèi)部實現(xiàn)了FMDatabase相關(guān)的創(chuàng)建及管理。
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
[queue inDatabase:^(FMDatabase *db) {
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];

    FMResultSet *rs = [db executeQuery:@"select * from foo"];
    while ([rs next]) {
        …
    }
}];
  • 執(zhí)行事務(wù)
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];

    if (whoopsSomethingWrongHappened) {
        *rollback = YES;
        return;
    }

    // etc ...
}];

更多事務(wù)相關(guān)知識參考這篇博客:數(shù)據(jù)庫事務(wù) ios FMDB

5.7 FMDatabase源碼解析

- (BOOL)openWithFlags:(int)flags vfs:(NSString *)vfsName {
#if SQLITE_VERSION_NUMBER >= 3005000
    if (_db) {
        return YES;
    }
    
    int err = sqlite3_open_v2([self sqlitePath], (sqlite3**)&_db, flags, [vfsName UTF8String]);
    if(err != SQLITE_OK) {
        NSLog(@"error opening!: %d", err);
        return NO;
    }
    
    if (_maxBusyRetryTimeInterval > 0.0) {
        // set the handler
        [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
    }
    
    return YES;
#else
    NSLog(@"openWithFlags requires SQLite 3.5");
    return NO;
#endif
}
  • vfs是虛擬文件系統(tǒng)的簡稱导盅,主要是用來統(tǒng)一不同平臺操作系統(tǒng)文件的訪問较幌,屏蔽底層硬件介質(zhì),提供統(tǒng)一的訪問接口白翻。
  • 我們可以看到通過 sqlite3_open_v2這個函數(shù)乍炉,在指定的path上面打開了數(shù)據(jù)庫的句柄:_db.
  • 后面,我們就可以拿這個句柄去訪問數(shù)據(jù)庫了滤馍,比如創(chuàng)建庫岛琼、創(chuàng)建表、插入數(shù)據(jù)巢株、更新數(shù)據(jù)槐瑞、查詢數(shù)據(jù)等。

5.8 FMResultSet源碼解析

- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
    
    // 先判斷這個句柄是否存在阁苞,即操作數(shù)據(jù)庫的入口
    if (![self databaseExists]) {
        return 0x00;
    }
    
    // 是否正在執(zhí)行查詢
    if (_isExecutingStatement) {
        [self warnInUse];
        return 0x00;
    }
    
    _isExecutingStatement = YES;
    
    int rc                  = 0x00;
    
    //定義一個stmt存放結(jié)果集
    sqlite3_stmt *pStmt     = 0x00;
    // 主要是做 銷毀stmt的工作困檩,防止內(nèi)存泄漏
    FMStatement *statement  = 0x00;
    FMResultSet *rs         = 0x00;
    
    if (_traceExecution && sql) {
        NSLog(@"%@ executeQuery: %@", self, sql);
    }
    
    if (_shouldCacheStatements) {
        statement = [self cachedStatementForQuery:sql];
        pStmt = statement ? [statement statement] : 0x00;
        [statement reset];
    }
    
    if (!pStmt) {
        
        rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
        
        if (SQLITE_OK != rc) {
            if (_logsErrors) {
                NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
                NSLog(@"DB Query: %@", sql);
                NSLog(@"DB Path: %@", _databasePath);
            }
            
            if (_crashOnErrors) {
                NSAssert(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
                abort();
            }
            
            sqlite3_finalize(pStmt);
            _isExecutingStatement = NO;
            return nil;
        }
    }
    
    id obj;
    int idx = 0;
    int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)
    
    // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
    if (dictionaryArgs) {
        
        for (NSString *dictionaryKey in [dictionaryArgs allKeys]) {
            
            // Prefix the key with a colon.
            NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey];
            
            if (_traceExecution) {
                NSLog(@"%@ = %@", parameterName, [dictionaryArgs objectForKey:dictionaryKey]);
            }
            
            // Get the index for the parameter name.
            int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]);
            
            FMDBRelease(parameterName);
            
            if (namedIdx > 0) {
                // Standard binding from here.
                [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
                // increment the binding count, so our check below works out
                idx++;
            }
            else {
                NSLog(@"Could not find index for %@", dictionaryKey);
            }
        }
    }
    else {
        
        while (idx < queryCount) {
            
            if (arrayArgs && idx < (int)[arrayArgs count]) {
                obj = [arrayArgs objectAtIndex:(NSUInteger)idx];
            }
            else if (args) {
                obj = va_arg(args, id);
            }
            else {
                //We ran out of arguments
                break;
            }
            
            if (_traceExecution) {
                if ([obj isKindOfClass:[NSData class]]) {
                    NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]);
                }
                else {
                    NSLog(@"obj: %@", obj);
                }
            }
            
            idx++;
            
            [self bindObject:obj toColumn:idx inStatement:pStmt];
        }
    }
    
    if (idx != queryCount) {
        NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)");
        sqlite3_finalize(pStmt);
        _isExecutingStatement = NO;
        return nil;
    }
    
    FMDBRetain(statement); // to balance the release below
    
    if (!statement) {
        statement = [[FMStatement alloc] init];
        [statement setStatement:pStmt];
        
        if (_shouldCacheStatements && sql) {
            [self setCachedStatement:statement forQuery:sql];
        }
    }
    
    // the statement gets closed in rs's dealloc or [rs close];
    rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
    [rs setQuery:sql];
    
    NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
    [_openResultSets addObject:openResultSet];
    
    [statement setUseCount:[statement useCount] + 1];
    
    FMDBRelease(statement);
    
    _isExecutingStatement = NO;
    
    return rs;
}

5.9 FMDatabaseQueue源碼解析

- (void)inDatabase:(void (^)(FMDatabase *db))block {
#ifndef NDEBUG
    /* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue
     * and then check it against self to make sure we're not about to deadlock. */
    FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
    assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
#endif
    
    FMDBRetain(self);
    
    dispatch_sync(_queue, ^() {
        
        FMDatabase *db = [self database];
        block(db);
        
        if ([db hasOpenResultSets]) {
            NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");
            
#if defined(DEBUG) && DEBUG
            NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]);
            for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
                FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
                NSLog(@"query: '%@'", [rs query]);
            }
#endif
        }
    });
    
    FMDBRelease(self);
}
  • FMDatabaseQueue 使用實例
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 1、查詢所有的日志那槽。
    NSArray *arrLog = [self dd_fetchAppLogs];
});

+ (NSArray *)dd_fetchAppLogs
{
    __block NSMutableArray *appLogs = nil;
    WSELF;
    [[FMDatabaseQueue sharedInstance] inDatabase:^(FMDatabase *db) {
        SSELF;
        FMResultSet *rs = [db executeQuery:@"SELECT * FROM STAppLog;"];
        
        @onExit {
            [rs close];
        };
        
        if (rs == nil)
        {
            STLogDBLastError(db);
            return;
        }
        
        while ([rs next])
        {
            @autoreleasepool {
                if (appLogs == nil) appLogs = [[NSMutableArray alloc] init];
                STAppLog *appLog = [self mj_objectWithKeyValues:rs.resultDictionary];
                [appLogs addObject:appLog];
            }
        }
    }];
    
    return appLogs;
}

6. CoreData

6.1 創(chuàng)建數(shù)據(jù)庫表

  • 在MesaSQLite設(shè)計器中創(chuàng)建表結(jié)構(gòu)悼沿,然后將生成的sql復(fù)制出來使用。這樣可以避免手敲代碼產(chǎn)生的錯誤骚灸。
  • 將sql保存成文件糟趾,然后放到xcode工程中。
coredata結(jié)構(gòu)

6.2 數(shù)據(jù)模型管理類NSManagedObjectModel

  • 通過NSManagedObjectModel甚牲,可以將創(chuàng)建的數(shù)據(jù)模型文件讀取為模型管理類對象义郑。實體類似于數(shù)據(jù)庫中的表結(jié)構(gòu)。

  • 創(chuàng)建NSManagedObjectModel對象

  • 首先丈钙、我們需要知道數(shù)據(jù)模型文件在哪魔慷,通過數(shù)據(jù)模型文件加載NSManagedObjectModel。

- (NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    
    NSURL *modelURL = [DDBITrackKitBUNDLE URLForResource:@"Model" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

6.3 持久化存儲協(xié)調(diào)者類NSPersistentStoreCoordinator

  • NSManagedObjectContext是進行數(shù)據(jù)管理的核心類著恩,我們通過這個類來進行數(shù)據(jù)的增刪改查等操作院尔。
  • PersistentStoreCoordinator對象的創(chuàng)建需要用到ManagedObjectModel對象蜻展,根據(jù)objectModel的模型結(jié)構(gòu)創(chuàng)建持久化的本地存儲。同時PersistentStoreCoordinator對象需要知道數(shù)據(jù)庫文件在哪里邀摆,以便打開對一個的數(shù)據(jù)庫纵顾。
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    
    // 指定一個數(shù)據(jù)庫文件路徑
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"DDBITrackKit.sqlite"];
    
    NSError *error = nil;
    // 通過ManagedObjectModel對象創(chuàng)建NSPersistentStoreCoordinator對象。
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    
    // 指定底層的存儲方式為SQLite數(shù)據(jù)庫栋盹。
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        //
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    
    return _persistentStoreCoordinator;
}

6.4 數(shù)據(jù)對象管理上下文NSManagedObjectContext

  • 創(chuàng)建NSManagedObjectContext對象

操作數(shù)據(jù)需要用到Object的句柄施逾,每個句柄管理了對應(yīng)的ObjectModel對象。一般我們會創(chuàng)建一個主句柄例获。然后使用子句柄執(zhí)行多線程操作汉额。
需要注意的點是:各個線程創(chuàng)建的子句柄相互之間是不能直接進行通信的,后果是有可能會崩潰榨汤,以及查詢不出數(shù)據(jù)蠕搜、或者查詢出來錯誤的數(shù)據(jù)。如果需要通信收壕,那么需要把實體(Entity)的ObjectID傳遞給另一個線程妓灌,然后這個線程里的context執(zhí)行對應(yīng)的查詢,然后再改變查詢出來的object的數(shù)據(jù)蜜宪。比較繁瑣虫埂。
保存數(shù)據(jù)的時候∑匝椋可以調(diào)用子句柄的save:函數(shù)掉伏,然后會在我們的通知中心里拿到這個數(shù)據(jù)改變的通知。

  • 創(chuàng)建主句柄
- (NSManagedObjectContext *)mainManagedObjectContext
{
    if (_mainManagedObjectContext != nil) {
        return _mainManagedObjectContext;
    }
    _mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    _mainManagedObjectContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
    return _mainManagedObjectContext;
}
  • 創(chuàng)建多線程的子句柄
- (NSManagedObjectContext *)privateContext
{
    // 設(shè)置一個 persistent store coordinator 和兩個獨立的 contexts 被證明了是在后臺處理 Core Data 的好方法澳窑。
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    context.persistentStoreCoordinator = [self persistentStoreCoordinator];
    return context;
}
  • 創(chuàng)建數(shù)據(jù)更改的通知
[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
        NSManagedObjectContext *moc = self.mainManagedObjectContext;
        if (note.object != moc) {
            [moc performBlock:^{
                [moc mergeChangesFromContextDidSaveNotification:note];
            }];
        }
    }];

6.5 查詢數(shù)據(jù)

  • 通過NSEntityDescription來查詢所需要的實體對象斧散。
+ (instancetype)findOrCreateWithIdentifier:(id)identifier inContext:(NSManagedObjectContext *)context
{
    id object = nil;
    NSString *entityName = NSStringFromClass(self);
    if (identifier)
    {
        NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];
        fetchRequest.predicate = [NSPredicate predicateWithFormat:@"appLogID = %@", identifier];
        fetchRequest.fetchLimit = 1;
        fetchRequest.returnsObjectsAsFaults = NO;
        NSError *error = nil;
        id object = [[context executeFetchRequest:fetchRequest error:&error] lastObject];
        if (object == nil) {
            object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
        }
    }
    else
    {
        object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
    }
    
    return object;
}
+ (NSArray *)dd_fetchAllAppLog
{
    NSManagedObjectContext *context = [DatabaseHelper shareInstance].store.privateContext;
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"DDBILog"];
    [request setReturnsObjectsAsFaults:NO];
    NSError *error;
    NSArray *arrResult = [context executeFetchRequest:request error:&error];
    return arrResult;
}

6.6 更新數(shù)據(jù)

  • 先查詢出來對象,然后更新對象響應(yīng)的字段照捡,最后調(diào)用save函數(shù)進行保存
- (void)dd_saveOrUpdateWithContext:(NSManagedObjectContext *)localContext completion:(DatabaseCompletion)completion
{
    DDBILog *tAppLog = [DDBILog findOrCreateWithIdentifier:self.appLogID inContext:localContext];
    tAppLog.accuracy         = self.accuracy;
    [localContext save:NULL];
    
    if (completion) {
        completion(YES, nil);
    }
}

6.7 刪除對象

- (void)dd_delete:(DatabaseCompletion)completion
{
    NSManagedObjectContext *localContext = [DatabaseHelper shareInstance].store.privateContext;
    [self dd_deleteWithContext:localContext completion:completion];
}

- (void)dd_deleteWithContext:(NSManagedObjectContext *)context completion:(DatabaseCompletion)completion
{
    [self MR_deleteEntityInContext:context];
    [context save:NULL];
}

7. WCDB

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市话侧,隨后出現(xiàn)的幾起案子栗精,更是在濱河造成了極大的恐慌,老刑警劉巖瞻鹏,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悲立,死亡現(xiàn)場離奇詭異,居然都是意外死亡新博,警方通過查閱死者的電腦和手機薪夕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赫悄,“玉大人原献,你說我怎么就攤上這事馏慨。” “怎么了姑隅?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵写隶,是天一觀的道長。 經(jīng)常有香客問我讲仰,道長慕趴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任鄙陡,我火速辦了婚禮冕房,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘趁矾。我一直安慰自己耙册,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布愈魏。 她就那樣靜靜地躺著觅玻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪培漏。 梳的紋絲不亂的頭發(fā)上溪厘,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天,我揣著相機與錄音牌柄,去河邊找鬼畸悬。 笑死,一個胖子當(dāng)著我的面吹牛珊佣,可吹牛的內(nèi)容都是我干的蹋宦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼咒锻,長吁一口氣:“原來是場噩夢啊……” “哼冷冗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起惑艇,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蒿辙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后滨巴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體思灌,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年恭取,在試婚紗的時候發(fā)現(xiàn)自己被綠了泰偿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡蜈垮,死狀恐怖耗跛,靈堂內(nèi)的尸體忽然破棺而出裕照,到底是詐尸還是另有隱情,我是刑警寧澤课兄,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布牍氛,位于F島的核電站,受9級特大地震影響烟阐,放射性物質(zhì)發(fā)生泄漏搬俊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一蜒茄、第九天 我趴在偏房一處隱蔽的房頂上張望唉擂。 院中可真熱鬧,春花似錦檀葛、人聲如沸玩祟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽空扎。三九已至,卻和暖如春润讥,著一層夾襖步出監(jiān)牢的瞬間转锈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工楚殿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留撮慨,地道東北人。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓脆粥,卻偏偏與公主長得像砌溺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子变隔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,446評論 2 359

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