IOS数据存储方式汇总

2019-08-26  本文已影响0人  孔雨露

@TOC

IOS数据存储简介

存储方式 优点 缺点 适用场景 备注
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属性列表)

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 *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)

//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数据库

- (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 简介

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

5.2 FMDB 创建数据库

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

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

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 查询

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 更新

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 删除

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

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

coredata结构

6.2 数据模型管理类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

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

操作数据需要用到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 查询数据

+ (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 更新数据

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

上一篇 下一篇

猜你喜欢

热点阅读