iOS數(shù)據(jù)存儲(chǔ)

如何存儲(chǔ)數(shù)據(jù)

  • 1掘殴、文件
  • 3、NSUserDefaults
  • 2爹谭、數(shù)據(jù)庫

文件

  • 1、沙盒
  • 2每聪、Plist
  • 3旦棉、NSKeyedArchiver歸檔 / NSKeyedUnarchiver解檔

NSUserDefaults

數(shù)據(jù)庫

  • 1、SQLite3
  • 2药薯、FMDB
  • 3、Core Data

文件

沙盒

iOS本地化存儲(chǔ)的數(shù)據(jù)保存在沙盒中救斑。

  • Documents:iTunes會(huì)備份該目錄童本。一般用來存儲(chǔ)需要持久化的數(shù)據(jù)。
  • Library/Caches:緩存脸候,iTunes不會(huì)備份該目錄穷娱。內(nèi)存不足時(shí)會(huì)被清除,應(yīng)用沒有運(yùn)行時(shí)运沦,可能會(huì)被清除泵额,。一般存儲(chǔ)體積大携添、不需要備份的非重要數(shù)據(jù)嫁盲。
  • Library/Preference:iTunes同會(huì)備份該目錄,可以用來存儲(chǔ)一些偏好設(shè)置烈掠。
  • tmp: iTunes不會(huì)備份這個(gè)目錄羞秤,用來保存臨時(shí)數(shù)據(jù),應(yīng)用退出時(shí)會(huì)清除該目錄下的數(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];

其中:

  • NSDocumentDirectory: 第一個(gè)參數(shù)代表要查找哪個(gè)文件瘾蛋,是一個(gè)枚舉。
  • NSUserDomainMask: 也是一個(gè)枚舉矫限,表示搜索的范圍限制于當(dāng)前應(yīng)用的沙盒目錄哺哼。。
  • YES (expandTilde): 第三個(gè)參數(shù)是一個(gè)BOOL值叼风。iOS中主目錄的全寫形式是/User/userName取董,這個(gè)參數(shù)填YES就表示全寫,填NO就是寫成‘‘~’’咬扇。

plist

可以把字典或數(shù)組直接寫入到文件中甲葬。另外,NSString懈贺、NSData经窖、NSNumber等類型坡垫,也可以使用writeToFile:atomically:方法直接將對(duì)象寫入文件中,只是Type為空画侣。

存儲(chǔ)

// 要保存的數(shù)據(jù)
NSDictionary *dict = [NSDictionary dictionaryWithObject:@"iOS cookbook" forKey:@"bookName"];
 
// 獲取路徑.
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
 
// 寫入數(shù)據(jù)
[dict writeToFile:filePath atomically:YES];

讀取

// 文件路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
 
// 解析數(shù)據(jù)
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSString *result = dict[@"bookName"];
NSLog(@"%@", result);

NSKeyedArchiver歸檔 / NSKeyedUnarchiver解檔

NSKeyedArchiver的三個(gè)使用場景:

  • 1.對(duì)單個(gè)簡單對(duì)象進(jìn)行歸檔/解檔冰悠。
// 獲取歸檔文件路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"knight"];
 
// 對(duì)字符串@”test”進(jìn)行歸檔,寫入到filePath中
[NSKeyedArchiver archiveRootObject:@"test" toFile:filePath];
 
// 根據(jù)保存數(shù)據(jù)的路徑filePath解檔數(shù)據(jù)
NSString *result = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
// 打印歸檔的數(shù)據(jù)
NSLog(@"%@",result);
  • 2.對(duì)多個(gè)對(duì)象進(jìn)行歸檔/解檔
// 獲取歸檔路徑
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test"];

// 用來承載數(shù)據(jù)的NSMutableData
NSMutableData *data = [[NSMutableData alloc] init];
// 歸檔對(duì)象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];

// 將要被保存的三個(gè)數(shù)據(jù)
NSString *name = @"jack";
int age = 18;
double height = 1.80;

// 運(yùn)用encodeObject:方法歸檔數(shù)據(jù)
[archiver encodeObject:name forKey:@"name"];
[archiver encodeInt:age forKey:@"age"];
[archiver encodeDouble:height forKey:@"height"];
// 結(jié)束歸檔
[archiver finishEncoding];
// 寫入數(shù)據(jù)(存儲(chǔ)數(shù)據(jù))
[data writeToFile:filePath atomically:YES];

// NSMutableData用來承載解檔出來的數(shù)據(jù)
NSMutableData *resultData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
// 解檔對(duì)象
NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:resultData];

// 分別解檔出三個(gè)數(shù)據(jù)
NSString *resultName = [unArchiver decodeObjectForKey:@"name"];
int resultAge = [unArchiver decodeIntForKey:@"age"];
double resultHeight = [unArchiver decodeDoubleForKey:@"height"];
//結(jié)束解檔
[unArchiver finishDecoding];
// 打印
NSLog(@"name = %@, age = %d, height = %.2f", resultName, resultAge, resultHeight);
  • 3.歸檔保存自定義對(duì)象
    如果想對(duì)一個(gè)自定義對(duì)象進(jìn)行歸檔解檔配乱,首先要讓對(duì)象遵守NSCoding協(xié)議溉卓。
@interface DDAppConfigModel : NSObject <NSCoding>
@property(nonatomic, copy) NSString *paramName;
@property(nonatomic, copy) NSString *paramValue;
@property(nonatomic, copy) NSString *version;
@property(nonatomic, copy) NSString *delFlag;
@property(nonatomic, copy) NSString *type;

- (instancetype)initWithDict:(NSDictionary *)dict;

- (void)updateWithDict:(NSDictionary *)dict;

@end

其中:NSCoding協(xié)議有2個(gè)方法:

- (void)encodeWithCoder:(NSCoder *)aCoder

歸檔時(shí)調(diào)用這個(gè)方法,在方法中使用encodeObject:forKey:歸檔變量搬泥。

- (instancetype)initWithCoder:(NSCoder *)aDecoder

解檔時(shí)調(diào)用這個(gè)方法桑寨,在方法中使用decodeObject:forKey讀出變量。

@implementation DDAppConfigModel

- (id)initWithCoder:(NSCoder *)aDecoder
{

  if (self = [super init])
  {
    self.paramName = [aDecoder decodeObjectForKey:@"paramName"];
    self.paramValue = [aDecoder decodeObjectForKey:@"paramValue"];
    self.version = [aDecoder decodeObjectForKey:@"version"];
    self.delFlag = [aDecoder decodeObjectForKey:@"delFlag"];
    self.type = [aDecoder decodeObjectForKey:@"type"];
  }
  return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
  [aCoder encodeObject:self.paramName forKey:@"paramName"];
  [aCoder encodeObject:self.paramValue forKey:@"paramValue"];
  [aCoder encodeObject:self.version forKey:@"version"];
  [aCoder encodeObject:self.delFlag forKey:@"delFlag"];
  [aCoder encodeObject:self.type forKey:@"type"];
}

//類方法忿檩,運(yùn)用NSKeyedArchiver歸檔數(shù)據(jù)
+ (void)saveConfig:(DDAppConfigModel *)config
{
    NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *path = [docPath stringByAppendingPathComponent:@"DDAppConfigModel.plist"];
    [NSKeyedArchiver archiveRootObject:config toFile:path];
}

//類方法尉尾,使用NSKeyedUnarchiver解檔數(shù)據(jù)
+ (DDAppConfigModel *)getConfig
{
    NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *path=[docPath stringByAppendingPathComponent:@"DDAppConfigModel.plist"];
    DDAppConfigModel *config = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    return config;
}
@end

NSUserDefaults

一般使用它來進(jìn)行一些設(shè)置的記錄,比如用戶ID燥透,開關(guān)是否打開等設(shè)置沙咏。通過鍵值對(duì)的方式記錄設(shè)置。

NSUserDefaults可以存儲(chǔ)的數(shù)據(jù)類型包括:NSData班套、NSString肢藐、NSNumber、NSDate吱韭、NSArray吆豹、NSDictionary。如果要存儲(chǔ)其他類型杉女,則需要轉(zhuǎn)換為前面的類型瞻讽,才能用NSUserDefaults存儲(chǔ)。

// 通用型設(shè)置數(shù)據(jù)
#define kSetUserDefaults(key, value)  ([USER_DEFAULT setObject:value forKey:key], [USER_DEFAULT synchronize])
// 通用型獲取數(shù)據(jù)
#define kUserDefaults(key) [USER_DEFAULT objectForKey:key]

數(shù)據(jù)庫

SQLite3

  • 添加庫文件libsqlite3.0.tbd
  • 導(dǎo)入頭文件#import
  • 打開數(shù)據(jù)庫
  • 創(chuàng)建表
  • 增刪改查操作
  • 關(guān)閉數(shù)據(jù)庫

推薦參考:https://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649286361&idx=1&sn=78bbcda7f41a14291ad71289e4821f71&scene=0#rd

FMDB

FMDB封裝了SQLite的C語言API熏挎,更加面向?qū)ο蟆?/p>

FMDB中的三個(gè)類:

  • FMDatabase:可以理解成一個(gè)數(shù)據(jù)庫速勇。
  • FMResultSet:查詢的結(jié)果集合。
  • FMDatabaseQueue:運(yùn)用多線程坎拐,可執(zhí)行多個(gè)查詢烦磁、更新。線程安全哼勇。

創(chuàng)建數(shù)據(jù)庫都伪。

FMDatabase創(chuàng)建的時(shí)候需要提供一個(gè)SQLite數(shù)據(jù)庫文件。我們一般提供一個(gè).db的文件路徑即可积担。

NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];

打開數(shù)據(jù)庫

在和數(shù)據(jù)庫進(jìn)行交互之前陨晶,我們需要先打開數(shù)據(jù)庫。使用上一步拿到的數(shù)據(jù)庫文件操作句柄db

if (![db open]) 
{
    db = nil;
    return;
}

創(chuàng)建表

使用集合語句來進(jìn)行表的創(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];

查詢

使用executeQuery 來執(zhí)行查詢語句先誉,F(xiàn)MResultSet來進(jìn)行存儲(chǔ)查詢的結(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]];
}

另外褐耳,FMResultSet可以通過如下的方法將數(shù)據(jù)通過恰當(dāng)?shù)母袷綑z索出來诈闺。

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

更新

通過調(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;
}

刪除數(shù)據(jù)

BOOL success = [db executeUpdate:@"DELETE FROM STAppLog WHERE userID = ? AND appLogID = ?;", self.userID, self.appLogID];
if (!success)
{
    STLogDBLastError(db);
    result = NO;
    return;
}

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

FMDatabaseQueue內(nèi)部實(shí)現(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]) {
        …
    }
}];

事務(wù)
參考:https://blog.csdn.net/x32sky/article/details/45531229

[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 ...
}];

關(guān)閉數(shù)據(jù)庫

[db close];

FMDatabase源碼解析

管理數(shù)據(jù)庫刃滓。需要指定一個(gè)文件用來存儲(chǔ)數(shù)據(jù)仁烹。例如:xxx/xxx/evian.db

然后,調(diào)用如下方法打開數(shù)據(jù)庫:

- (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ái)操作系統(tǒng)文件的訪問晃危,屏蔽底層硬件介質(zhì),提供統(tǒng)一的訪問接口老客。

我們可以看到通過 sqlite3_open_v2這個(gè)函數(shù),在指定的path上面打開了數(shù)據(jù)庫的句柄:_db.
后面震叮,我們就可以拿這個(gè)句柄去訪問數(shù)據(jù)庫了胧砰,比如創(chuàng)建庫、創(chuàng)建表苇瓣、插入數(shù)據(jù)尉间、更新數(shù)據(jù)、查詢數(shù)據(jù)等击罪。

FMResultSet源碼解析

下面我們來看看FMDatabase最主要的一個(gè)接口:

- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
    
    // 先判斷這個(gè)句柄是否存在哲嘲,即操作數(shù)據(jù)庫的入口
    if (![self databaseExists]) {
        return 0x00;
    }
    
    // 是否正在執(zhí)行查詢
    if (_isExecutingStatement) {
        [self warnInUse];
        return 0x00;
    }
    
    _isExecutingStatement = YES;
    
    int rc                  = 0x00;
    
    //定義一個(gè)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;
}

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);
}

如何使用呢媳禁,請(qǐng)看如下調(diào)用:

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;
}

通過源碼可以知道姜贡,該查詢操作將在FMDB內(nèi)部創(chuàng)建的隊(duì)列中執(zhí)行子線程的查詢操作功舀。

CoreData

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

在MesaSQLite設(shè)計(jì)器中創(chuàng)建表結(jié)構(gòu),然后將生成的sql復(fù)制出來使用毫别。這樣可以避免手敲代碼產(chǎn)生的錯(cuò)誤娃弓。
將sql保存成文件,然后放到xcode工程中岛宦。


coreData.png

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

通過NSManagedObjectModel台丛,可以將創(chuàng)建的數(shù)據(jù)模型文件讀取為模型管理類對(duì)象。實(shí)體類似于數(shù)據(jù)庫中的表結(jié)構(gòu)砾肺。

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

NSPersistentStoreCoordinator建立數(shù)據(jù)模型與本地文件或數(shù)據(jù)庫之間的聯(lián)系挽霉,通過它將本地?cái)?shù)據(jù)讀入內(nèi)存或者將修改過的臨時(shí)數(shù)據(jù)進(jìn)行持久化的保存防嗡。

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

NSManagedObjectContext是進(jìn)行數(shù)據(jù)管理的核心類,我們通過這個(gè)類來進(jìn)行數(shù)據(jù)的增刪改查等操作炼吴。

如何使用

一般我們需要在工程里創(chuàng)建一個(gè)xxx.xcdatamodeld文件本鸣。然后在這個(gè)文件里面創(chuàng)建表

coreData1.png

創(chuàng)建NSManagedObjectModel對(duì)象

首先、我們需要知道數(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;
}

創(chuàng)建NSPersistentStoreCoordinator對(duì)象

PersistentStoreCoordinator對(duì)象的創(chuàng)建需要用到ManagedObjectModel對(duì)象,根據(jù)objectModel的模型結(jié)構(gòu)創(chuàng)建持久化的本地存儲(chǔ)童芹。同時(shí)PersistentStoreCoordinator對(duì)象需要知道數(shù)據(jù)庫文件在哪里涮瞻,以便打開對(duì)一個(gè)的數(shù)據(jù)庫。

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    
    // 指定一個(gè)數(shù)據(jù)庫文件路徑
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"DDBITrackKit.sqlite"];
    
    NSError *error = nil;
    // 通過ManagedObjectModel對(duì)象創(chuàng)建NSPersistentStoreCoordinator對(duì)象假褪。
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    
    // 指定底層的存儲(chǔ)方式為SQLite數(shù)據(jù)庫署咽。
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        //
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    
    return _persistentStoreCoordinator;
}

創(chuàng)建NSManagedObjectContext對(duì)象。

操作數(shù)據(jù)需要用到Object的句柄生音,每個(gè)句柄管理了對(duì)應(yīng)的ObjectModel對(duì)象宁否。一般我們會(huì)創(chuàng)建一個(gè)主句柄。然后使用子句柄執(zhí)行多線程操作缀遍。

需要注意的點(diǎn)是:各個(gè)線程創(chuàng)建的子句柄相互之間是不能直接進(jìn)行通信的慕匠,后果是有可能會(huì)崩潰,以及查詢不出數(shù)據(jù)域醇、或者查詢出來錯(cuò)誤的數(shù)據(jù)台谊。如果需要通信,那么需要把實(shí)體(Entity)的ObjectID傳遞給另一個(gè)線程譬挚,然后這個(gè)線程里的context執(zhí)行對(duì)應(yīng)的查詢锅铅,然后再改變查詢出來的object的數(shù)據(jù)。比較繁瑣减宣。

保存數(shù)據(jù)的時(shí)候盐须。可以調(diào)用子句柄的save:函數(shù)蚪腋,然后會(huì)在我們的通知中心里拿到這個(gè)數(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è)置一個(gè) persistent store coordinator 和兩個(gè)獨(dú)立的 contexts 被證明了是在后臺(tái)處理 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];
            }];
        }
    }];

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

通過NSEntityDescription來查詢所需要的實(shí)體對(duì)象立帖。

+ (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;
}

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

先查詢出來對(duì)象,然后更新對(duì)象響應(yīng)的字段悠砚,最后調(diào)用save函數(shù)進(jìn)行保存

- (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);
    }
}

刪除對(duì)象

- (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];
}

mr 的內(nèi)部實(shí)現(xiàn)

- (BOOL) MR_deleteEntityInContext:(NSManagedObjectContext *)context
{
    NSError *error = nil;
    NSManagedObject *entityInContext = [context existingObjectWithID:[self objectID] error:&error];
    [MagicalRecord handleErrors:error];
    if (entityInContext) {
        [context deleteObject:entityInContext];
    }

    return YES;
}

通過這里可以看到晓勇,線程之間傳遞數(shù)據(jù)是通過objectID查詢出來對(duì)應(yīng)的實(shí)體的。然后調(diào)用contextdeleteObject函數(shù)將這個(gè)實(shí)體刪除。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绑咱,一起剝皮案震驚了整個(gè)濱河市绰筛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌描融,老刑警劉巖铝噩,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異窿克,居然都是意外死亡骏庸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門年叮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來具被,“玉大人,你說我怎么就攤上這事只损∫蛔耍” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵跃惫,是天一觀的道長叮叹。 經(jīng)常有香客問我,道長爆存,這世上最難降的妖魔是什么衬横? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮终蒂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遥诉。我一直安慰自己拇泣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布矮锈。 她就那樣靜靜地躺著霉翔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苞笨。 梳的紋絲不亂的頭發(fā)上债朵,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音瀑凝,去河邊找鬼序芦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛粤咪,可吹牛的內(nèi)容都是我干的谚中。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼宪塔!你這毒婦竟也來了磁奖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤某筐,失蹤者是張志新(化名)和其女友劉穎比搭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體南誊,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡身诺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弟疆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戚长。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖怠苔,靈堂內(nèi)的尸體忽然破棺而出同廉,到底是詐尸還是另有隱情,我是刑警寧澤柑司,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布迫肖,位于F島的核電站,受9級(jí)特大地震影響攒驰,放射性物質(zhì)發(fā)生泄漏蟆湖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一玻粪、第九天 我趴在偏房一處隱蔽的房頂上張望隅津。 院中可真熱鬧,春花似錦劲室、人聲如沸伦仍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽充蓝。三九已至,卻和暖如春喉磁,著一層夾襖步出監(jiān)牢的瞬間谓苟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國打工协怒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涝焙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓孕暇,卻偏偏與公主長得像纱皆,于是被迫代替她去往敵國和親湾趾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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

  • [TOC] 一派草、應(yīng)用的文件結(jié)構(gòu) I. 應(yīng)用沙盒 每個(gè)iOS應(yīng)用都有一個(gè) 應(yīng)用沙盒「文件系統(tǒng)目錄」搀缠,與其他文件系統(tǒng)隔...
    _涼風(fēng)_閱讀 1,615評(píng)論 2 8
  • 上一篇介紹了只能存儲(chǔ)特定對(duì)象(即非自定義對(duì)象)的NSUserDefaults艺普、wirteToFile:及Pli...
    ninazhang閱讀 1,236評(píng)論 0 1
  • 在iOS開發(fā)中必不可少的要用到數(shù)據(jù)存儲(chǔ),數(shù)據(jù)的處理是iOS開發(fā)中的核心技術(shù)鉴竭,適當(dāng)?shù)膶?duì)數(shù)據(jù)進(jìn)行持久化存儲(chǔ)可以實(shí)現(xiàn)應(yīng)用...
    一生信仰閱讀 533評(píng)論 0 0
  • 早上七點(diǎn)起來歧譬,不想運(yùn)動(dòng)。先出卷搏存,到九點(diǎn)開始運(yùn)動(dòng)瑰步。昨天婦保檢查,婦科沒有問題璧眠,還是配了栓劑缩焦,10天的量。換了兩張護(hù)墊...
    行一館閱讀 166評(píng)論 0 0
  • 16歲時(shí)寫了一篇文章责静,大致意思是發(fā)現(xiàn)了學(xué)校的美袁滥。校報(bào)工作的朋友想要拿去發(fā)表,當(dāng)時(shí)自己很倔強(qiáng)的說:“我才不要灾螃,我又不...
    皇后鎮(zhèn)閱讀 303評(píng)論 0 2