iOS数据存储

如何存储数据

  • 1、文件
  • 3、NSUserDefaults
  • 2、数据库

文件

  • 1、沙盒
  • 2、Plist
  • 3、NSKeyedArchiver归档 / NSKeyedUnarchiver解档

NSUserDefaults

数据库

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

文件

沙盒

iOS本地化存储的数据保存在沙盒中。

  • Documents:iTunes会备份该目录。一般用来存储需要持久化的数据。
  • Library/Caches:缓存,iTunes不会备份该目录。内存不足时会被清除,应用没有运行时,可能会被清除,。一般存储体积大、不需要备份的非重要数据。
  • Library/Preference:iTunes同会备份该目录,可以用来存储一些偏好设置。
  • tmp: iTunes不会备份这个目录,用来保存临时数据,应用退出时会清除该目录下的数据。

获取沙盒文件:

// 获取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: 第一个参数代表要查找哪个文件,是一个枚举。
  • NSUserDomainMask: 也是一个枚举,表示搜索的范围限制于当前应用的沙盒目录。。
  • YES (expandTilde): 第三个参数是一个BOOL值。iOS中主目录的全写形式是/User/userName,这个参数填YES就表示全写,填NO就是写成‘‘~’’。

plist

可以把字典或数组直接写入到文件中。另外,NSString、NSData、NSNumber等类型,也可以使用writeToFile:atomically:方法直接将对象写入文件中,只是Type为空。

存储

// 要保存的数据
NSDictionary *dict = [NSDictionary dictionaryWithObject:@"iOS cookbook" forKey:@"bookName"];
 
// 获取路径.
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];
 
// 写入数据
[dict writeToFile:filePath atomically:YES];

读取

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

NSKeyedArchiver归档 / NSKeyedUnarchiver解档

NSKeyedArchiver的三个使用场景:

  • 1.对单个简单对象进行归档/解档。
// 获取归档文件路径
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"knight"];
 
// 对字符串@”test”进行归档,写入到filePath中
[NSKeyedArchiver archiveRootObject:@"test" toFile:filePath];
 
// 根据保存数据的路径filePath解档数据
NSString *result = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
// 打印归档的数据
NSLog(@"%@",result);
  • 2.对多个对象进行归档/解档
// 获取归档路径
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"test"];

// 用来承载数据的NSMutableData
NSMutableData *data = [[NSMutableData alloc] init];
// 归档对象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];

// 将要被保存的三个数据
NSString *name = @"jack";
int age = 18;
double height = 1.80;

// 运用encodeObject:方法归档数据
[archiver encodeObject:name forKey:@"name"];
[archiver encodeInt:age forKey:@"age"];
[archiver encodeDouble:height forKey:@"height"];
// 结束归档
[archiver finishEncoding];
// 写入数据(存储数据)
[data writeToFile:filePath atomically:YES];

// NSMutableData用来承载解档出来的数据
NSMutableData *resultData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
// 解档对象
NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:resultData];

// 分别解档出三个数据
NSString *resultName = [unArchiver decodeObjectForKey:@"name"];
int resultAge = [unArchiver decodeIntForKey:@"age"];
double resultHeight = [unArchiver decodeDoubleForKey:@"height"];
//结束解档
[unArchiver finishDecoding];
// 打印
NSLog(@"name = %@, age = %d, height = %.2f", resultName, resultAge, resultHeight);
  • 3.归档保存自定义对象
    如果想对一个自定义对象进行归档解档,首先要让对象遵守NSCoding协议。
@interface DDAppConfigModel : NSObject 
@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协议有2个方法:

- (void)encodeWithCoder:(NSCoder *)aCoder

归档时调用这个方法,在方法中使用encodeObject:forKey:归档变量。

- (instancetype)initWithCoder:(NSCoder *)aDecoder

解档时调用这个方法,在方法中使用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"];
}

//类方法,运用NSKeyedArchiver归档数据
+ (void)saveConfig:(DDAppConfigModel *)config
{
    NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *path = [docPath stringByAppendingPathComponent:@"DDAppConfigModel.plist"];
    [NSKeyedArchiver archiveRootObject:config toFile:path];
}

//类方法,使用NSKeyedUnarchiver解档数据
+ (DDAppConfigModel *)getConfig
{
    NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *path=[docPath stringByAppendingPathComponent:@"DDAppConfigModel.plist"];
    DDAppConfigModel *config = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    return config;
}
@end

NSUserDefaults

一般使用它来进行一些设置的记录,比如用户ID,开关是否打开等设置。通过键值对的方式记录设置。

NSUserDefaults可以存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要存储其他类型,则需要转换为前面的类型,才能用NSUserDefaults存储。

// 通用型设置数据
#define kSetUserDefaults(key, value)  ([USER_DEFAULT setObject:value forKey:key], [USER_DEFAULT synchronize])
// 通用型获取数据
#define kUserDefaults(key) [USER_DEFAULT objectForKey:key]

数据库

SQLite3

  • 添加库文件libsqlite3.0.tbd
  • 导入头文件#import
  • 打开数据库
  • 创建表
  • 增删改查操作
  • 关闭数据库

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

FMDB

FMDB封装了SQLite的C语言API,更加面向对象。

FMDB中的三个类:

  • FMDatabase:可以理解成一个数据库。
  • FMResultSet:查询的结果集合。
  • FMDatabaseQueue:运用多线程,可执行多个查询、更新。线程安全。

创建数据库。

FMDatabase创建的时候需要提供一个SQLite数据库文件。我们一般提供一个.db的文件路径即可。

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

打开数据库

在和数据库进行交互之前,我们需要先打开数据库。使用上一步拿到的数据库文件操作句柄db

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

创建表

使用集合语句来进行表的创建。

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 来执行查询语句,FMResultSet来进行存储查询的结果。

FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
    //retrieve values for each record
}

可通过如下的方式将结果集里面的数据取出来。

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

另外,FMResultSet可以通过如下的方法将数据通过恰当的格式检索出来。

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

更新

通过调用- (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments执行插入、删除或者更新数据。
插入数据或者更新数据。

NSString *sql = ![rs next] ? INSERT_STATEMENT : UPDATE_STATEMENT;
BOOL success = [db executeUpdate:sql withParameterDictionary:[self pr_toDBDictionary]];
if (!success)
{
    STLogDBLastError(db);
    result = NO;
    return;
}

删除数据

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

多线程操作数据库

FMDatabaseQueue内部实现了FMDatabase相关的创建及管理。

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]) {
        …
    }
}];

事务
参考: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 ...
}];

关闭数据库

[db close];

FMDatabase源码解析

管理数据库。需要指定一个文件用来存储数据。例如:xxx/xxx/evian.db

然后,调用如下方法打开数据库:

- (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是虚拟文件系统的简称,主要是用来统一不同平台操作系统文件的访问,屏蔽底层硬件介质,提供统一的访问接口。

我们可以看到通过 sqlite3_open_v2这个函数,在指定的path上面打开了数据库的句柄:_db.
后面,我们就可以拿这个句柄去访问数据库了,比如创建库、创建表、插入数据、更新数据、查询数据等。

FMResultSet源码解析

下面我们来看看FMDatabase最主要的一个接口:

- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
    
    // 先判断这个句柄是否存在,即操作数据库的入口
    if (![self databaseExists]) {
        return 0x00;
    }
    
    // 是否正在执行查询
    if (_isExecutingStatement) {
        [self warnInUse];
        return 0x00;
    }
    
    _isExecutingStatement = YES;
    
    int rc                  = 0x00;
    
    //定义一个stmt存放结果集
    sqlite3_stmt *pStmt     = 0x00;
    // 主要是做 销毁stmt的工作,防止内存泄漏
    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);
}

如何使用呢,请看如下调用:

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内部创建的队列中执行子线程的查询操作。

CoreData

创建数据库表

在MesaSQLite设计器中创建表结构,然后将生成的sql复制出来使用。这样可以避免手敲代码产生的错误。
将sql保存成文件,然后放到xcode工程中。


iOS数据存储_第1张图片
coreData.png

数据模型管理类NSManagedObjectModel

通过NSManagedObjectModel,可以将创建的数据模型文件读取为模型管理类对象。实体类似于数据库中的表结构。

持久化存储协调者类NSPersistentStoreCoordinator

NSPersistentStoreCoordinator建立数据模型与本地文件或数据库之间的联系,通过它将本地数据读入内存或者将修改过的临时数据进行持久化的保存。

数据对象管理上下文NSManagedObjectContext

NSManagedObjectContext是进行数据管理的核心类,我们通过这个类来进行数据的增删改查等操作。

如何使用

一般我们需要在工程里创建一个xxx.xcdatamodeld文件。然后在这个文件里面创建表

iOS数据存储_第2张图片
coreData1.png

创建NSManagedObjectModel对象

首先、我们需要知道数据模型文件在哪,通过数据模型文件加载NSManagedObjectModel。

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

创建NSPersistentStoreCoordinator对象

PersistentStoreCoordinator对象的创建需要用到ManagedObjectModel对象,根据objectModel的模型结构创建持久化的本地存储。同时PersistentStoreCoordinator对象需要知道数据库文件在哪里,以便打开对一个的数据库。

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    
    // 指定一个数据库文件路径
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"DDBITrackKit.sqlite"];
    
    NSError *error = nil;
    // 通过ManagedObjectModel对象创建NSPersistentStoreCoordinator对象。
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    
    // 指定底层的存储方式为SQLite数据库。
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        //
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    
    return _persistentStoreCoordinator;
}

创建NSManagedObjectContext对象。

操作数据需要用到Object的句柄,每个句柄管理了对应的ObjectModel对象。一般我们会创建一个主句柄。然后使用子句柄执行多线程操作。

需要注意的点是:各个线程创建的子句柄相互之间是不能直接进行通信的,后果是有可能会崩溃,以及查询不出数据、或者查询出来错误的数据。如果需要通信,那么需要把实体(Entity)的ObjectID传递给另一个线程,然后这个线程里的context执行对应的查询,然后再改变查询出来的object的数据。比较繁琐。

保存数据的时候。可以调用子句柄的save:函数,然后会在我们的通知中心里拿到这个数据改变的通知。

创建主句柄

- (NSManagedObjectContext *)mainManagedObjectContext
{
    if (_mainManagedObjectContext != nil) {
        return _mainManagedObjectContext;
    }
    _mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    _mainManagedObjectContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
    return _mainManagedObjectContext;
}

创建多线程的子句柄

- (NSManagedObjectContext *)privateContext
{
    // 设置一个 persistent store coordinator 和两个独立的 contexts 被证明了是在后台处理 Core Data 的好方法。
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    context.persistentStoreCoordinator = [self persistentStoreCoordinator];
    return context;
}

创建数据更改的通知。

[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
        NSManagedObjectContext *moc = self.mainManagedObjectContext;
        if (note.object != moc) {
            [moc performBlock:^{
                [moc mergeChangesFromContextDidSaveNotification:note];
            }];
        }
    }];

查询数据

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

更新数据

先查询出来对象,然后更新对象响应的字段,最后调用save函数进行保存

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

删除对象

- (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 的内部实现

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

通过这里可以看到,线程之间传递数据是通过objectID查询出来对应的实体的。然后调用contextdeleteObject函数将这个实体删除。

你可能感兴趣的:(iOS数据存储)