IOS数据存储方式汇总

  • IOS数据存储简介
  • IOS 沙盒存储路径
  • IOS数据存储方式
  • 1. PList(XML属性列表)
  • 2. 偏好设置(NSUserDefaults)
  • 3. 归档(NSCoding NSKeyedArchiver NSKeyedUnarchiver)
  • 4. SQLITE数据库
  • 5. FMDB
  • 5.1 FMDB 简介
  • 5.2 FMDB 创建数据库
  • 5.3 FMDB 打开数据库,关闭数据库
  • 5.4 FMDB 创建表
  • 5.5 FMDB 增,删,改,查
  • 5.5.1 查询
  • 5.5.2 更新
  • 5.5.3 删除
  • 5.5.4 新增
  • 5.6 FMDB 多线程操作数据库
  • 5.7 FMDatabase源码解析
  • 5.8 FMResultSet源码解析
  • 5.9 FMDatabaseQueue源码解析
  • 6. CoreData
  • 6.1 创建数据库表
  • 6.2 数据模型管理类NSManagedObjectModel
  • 6.3 持久化存储协调者类NSPersistentStoreCoordinator
  • 6.4 数据对象管理上下文NSManagedObjectContext
  • 6.5 查询数据
  • 6.6 更新数据
  • 6.7 删除对象
  • 7. WCDB


IOS数据存储简介

  • 在项目开发当中,我们经常会对一些数据进行本地缓存处理。离线缓存的数据一般都保存在APP所在的沙盒之中。一般有以下几种:

存储方式

优点

缺点

适用场景

备注

PList(XML属性列表)

快速效率高

不灵活,只能存储常用的类型

只适用于系统自带的一些常用类型才能用

备注

偏好设置(NSUserDefaults)

快速效率高,简单易用

只能存储常用的类型

适用场景

备注

归档(NSCoding NSKeyedArchiver NSKeyedUnarchiver)

归档可以实现把自定义的对象存放在文件中

必须实现协议,浸入性强

适用场景

备注

SQLITE数据库

灵活

操作复杂

适用场景

备注

FMDB

是对sqlite的封装,简单易用,接口比原生的SQLite接口简洁很多,提供一些多线程,缓存,线程池的功能

缺点

适用场景

备注

CoreData

苹果自带的数据存储方式

缺点

适用场景

备注

WCDB

腾讯开发的一款DB工具

缺点

适用场景

备注

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

IOS数据存储方式

1. PList(XML属性列表)

  • 在使用plist进行数据存储和读取,只适用于系统自带的一些常用类型才能用,且必须先获取路径相对麻烦
  • 可以把字典或数组直接写入到文件中。另外,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. 偏好设置(NSUserDefaults)

  • 将所有的东西都保存在同一个文件夹下面,且主要用于存储应用的设置信息)
  • NSUserDefaults可以存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要存储其他类型,则需要转换为前面的类型,才能用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存储都有一个致命的缺陷,只能存储常用的类型。归档可以实现把自定义的对象存放在文件中。
  • 需要保存的对象必须遵守NSCoding协议,并且实现该协议中- (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数据库

  • 上述三种方法都无法存储大批量的数据,有性能的问题。
  • SQLITE作为程序员都比较熟悉,它是跨平台的数据库工具。下面简单介绍一下,如何打开数据库,新增一张表格,然后对其进行增删改查的操作。
  • 详细SQLite相关性能优化可以参考这篇文章:微信iOS SQLite源码优化实践
  • 实例4
    OC代码
- (void)openDB{
    //获取数据库文件路径
    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *fileName = [doc stringByAppendingPathComponent:@"students.sqlite"];

    //将OC字符串转换为c语言的字符串
    const char *cfileName = fileName.UTF8String;

    //打开数据库文件(如果数据库文件不存在,那么该函数会自动创建数据库文件)
    int result = sqlite3_open(cfileName, &_db);
    if (result == SQLITE_OK) {//打开成功
        NSLog(@"成功打开数据库");
    }else{
        NSLog(@"打开数据库失败");
    }
}


- (void)createTable{
    //创建表
    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(@"创建表成功");
    }else{
        NSLog(@"创建表失败---%s",errmsg);
    }
}


- (void)insertData{
    //插入数据
    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];

         //执行SQL语句
        char *errmsg = NULL;
        sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errmsg);
        if (errmsg) {//如果有错误信息
            NSLog(@"插入数据失败--%s",errmsg);
        }else{
            NSLog(@"插入数据成功");
        }
    }
}


- (void)deleteData{
    //删除age小于15的数据
    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(@"删除数据失败");
    }else{
        NSLog(@"删除数据成功");
    }
}

- (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(@"更新数据失败");
    }else{
        NSLog(@"更新数据成功");
    }
}


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

    //进行查询前的准备工作
    if(sqlite3_prepare_v2(_db, sql, -1, &stmt, NULL)==SQLITE_OK){//SQL语句没有问题
        NSLog(@"查询语句没有问题");

        //每调用一次sqlite3_step函数,stmt就会指向下一条记录
        while (sqlite3_step(stmt)==SQLITE_ROW) {//找到一条记录
            //取出数据
            //(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的也应该都耳熟能详了,项目中都用过。是一个很牛逼的DB工具。
  • FMDB 是对SQLIite数据库的C语言接口进行了一层封装,使其满足面向对象的操作,接口比原生的SQLite接口简洁很多。同时也提供一些多线程,缓存,线程池的功能。
  • FMDB的三个类:


功能

备注

FMDatabase

可以理解成一个数据库

备注

FMResultSet

查询的结果集合

备注

FMDatabaseQueue

运用多线程,可执行多个查询、更新,线程安全

备注

5.2 FMDB 创建数据库

  • FMDatabase创建的时候需要提供一个SQLite数据库文件。我们一般提供一个.db的文件路径即可。
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];

5.3 FMDB 打开数据库,关闭数据库

  • 打开数据库:在和数据库进行交互之前,我们需要先打开数据库。使用上一步拿到的数据库文件操作句柄db
if (![db open]) 
{
    db = nil;
    return;
}
  • 关闭数据库
[db close];

5.4 FMDB 创建表

  • 使用集合语句来进行表的创建。
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 来执行查询语句,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:

5.5.2 更新

  • 通过调用- (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;
}

5.5.3 删除

  • 通过执行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 多线程操作数据库

  • 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]) {
        …
    }
}];
  • 执行事务
[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 ...
}];

更多事务相关知识参考这篇博客:数据库事务 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是虚拟文件系统的简称,主要是用来统一不同平台操作系统文件的访问,屏蔽底层硬件介质,提供统一的访问接口。
  • 我们可以看到通过 sqlite3_open_v2这个函数,在指定的path上面打开了数据库的句柄:_db.
  • 后面,我们就可以拿这个句柄去访问数据库了,比如创建库、创建表、插入数据、更新数据、查询数据等。

5.8 FMResultSet源码解析

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

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 创建数据库表

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

ios 存储数据方式 存储iphone_sql

6.2 数据模型管理类NSManagedObjectModel

  • 通过NSManagedObjectModel,可以将创建的数据模型文件读取为模型管理类对象。实体类似于数据库中的表结构。
  • 创建NSManagedObjectModel对象
  • 首先、我们需要知道数据模型文件在哪,通过数据模型文件加载NSManagedObjectModel。
- (NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    
    NSURL *modelURL = [DDBITrackKitBUNDLE URLForResource:@"Model" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

6.3 持久化存储协调者类NSPersistentStoreCoordinator

  • NSManagedObjectContext是进行数据管理的核心类,我们通过这个类来进行数据的增删改查等操作。
  • 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;
}

6.4 数据对象管理上下文NSManagedObjectContext

  • 创建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];
            }];
        }
    }];

6.5 查询数据

  • 通过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 更新数据

  • 先查询出来对象,然后更新对象响应的字段,最后调用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);
    }
}

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