CoreData多线程实现初探(附源码)

由于历史的原因,项目中的火车票城市列表一直采用的全量更新,即:如果需要增删改某几个城市,则必须把app本地缓存的城市列表全部删除重新从接口拉取一份最新的数据。现在为了优化用户体验,需要改成增量更新的方式。
一开始并没有考虑到多线程的问题,直接使用了项目中封装好的基于CoreData的DBHelper工具类进行开发。QA期间测试进行几千条城市数据的增删改才发现,中途会有2s~3s的卡顿。于是周末花了半天时间学习了下CoreData的多线程开发。

为什么不用GCD包一层异步去处理这些耗时操作?

_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
  • CoreData不是线程安全的,对于ManagedObject以及ManagedObjectContext的访问,都只能在MOC初始化所指定的线程上进行(NSMainQueueConcurrencyType、NSPrivateQueueConcurrencyType),而不能跨线程。

  • 对于CoreData的多线程,苹果有自己的一套解决方案,并不能简单的将MOC从一个线程中传递到另一个线程中使用。

多种方案简介

多线程方案之child/parent context

ChildContext和ParentContext是相互独立的。只有当ChildContext中调用save了以后,才会把这段时间来Context的变化提交到ParentContext中,ChildContext并不会直接提交到NSPersistentStoreCoordinator中, parentContext就相当于它的NSPersistentStoreCoordinator。

注意:子线程的save操作并没有任何关于Disk IO的操作。而后mainContext在UI线程又要执行一次save操作才能真正将数据变动写进数据库中,这里的save操作就与Disk IO有关了,而且又是在主线程,所以说这种设计是最阻碍UI线程的。

CoreData多线程实现初探(附源码)_第1张图片
方案一.png
多线程方案之Notification

简单来说,就是不同的线程使用不同的context进行操作,当一个线程的context发生变化后,利用notification来通知另一个线程Context,另一个线程调用mergeChangesFromContextDidSaveNotification
来合并变化。

persistentStoreCoordinator<-mainContext, persistentStoreCoordinator<-privateContext

首先在ViewController中要添加一个名为NSManagedObjectContextDidSaveNotification的通知
,然后子线程中创建privateContext,进行数据增删改查操作,直接save到本地数据库,这时在ViewController中会回调之前注册的NSManagedObjectContextDidSaveNotification的回调方法,在该方法中调用mainContext的mergeChangesFromContextDidSaveNotification:notification方法,将所有的数据变动merge到mainContext中,这样就保持了两个Context中的数据同步。由于大部分的操作都是privateContext在子线程中操作的,所以这种设计是UI线程耗时最少的一种设计,但是它的代价是需要多写mergeChanges的方法。(注:前两种parent,child的Context,一旦childContext调用save方法,其parentContext不用任何merge操作,CoreData自动将数据merge到parentContext当中)

多线程方案之改良版child/parent context(本文实现)
理清关系
persistentStoreCoordinator<-backgroundContext<-mainContext<-privateContext

这种设计是第一种的改进设计,也是上述的老外博主推荐的一种设计方式。它总共有三个Context,一是连接persistentStoreCoordinator也是最底层的backgroundContext,二是UI线程的mainContext,三是子线程的privateContext,他们的关系如下:

privateContext.parentContext = mainContext, mainContext.parentContext = backgroundContext。
工作流程

在应用中,如果我们有API操作,首先我们会起一个子线程进行API请求,在得到Response后要进行数据库操作,这是我们要创建一个privateContext进行数据的增删改查,然后call privateContext的save方法进行存储,这里的save操作只是将所有数据变动Push up到它的父Context中也就是mainContext中,然后mainContext继续call save方法,将数据变动Push up到它的父Context中也就是backgroundContext,最后调用backgroundContext的save方法真正将数据变动存储到Disk数据库中,在这个过程中,前两个save操作相对耗时较少,真正耗时的操作是最后backgroundContext的save操作,因为只有它有Disk IO的操作。

干货
三个Context的初始化
appDelegate.m中的实现
-(NSManagedObjectContext *)rootObjectContext {//对应上述backgroundContext
    if (nil != _rootObjectContext) {
        return _rootObjectContext;
    }
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _rootObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_rootObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _rootObjectContext;
}

- (NSManagedObjectContext *)managedAsyncObjectContext
{//对应mainContext
    if (_managedAsyncObjectContext != nil) {
        return _managedAsyncObjectContext;
    }
    _managedAsyncObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    _managedAsyncObjectContext.parentContext = [self rootObjectContext];
    return _managedAsyncObjectContext;
}

- (NSManagedObjectContext *)privateAsyncObjectContext
{
    if (_privateAsyncObjectContext != nil) {
        return _privateAsyncObjectContext;
    }
    _privateAsyncObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [_privateAsyncObjectContext setParentContext:[self managedAsyncObjectContext]];
    return _privateAsyncObjectContext;
}
mainContext的save方法
AppDelegate中saveContext方法,每次privateContext调用save方法成功之后都要call这个方法  
- (void)saveAsyncContextWithWait:(BOOL)needWait
{
    NSManagedObjectContext *managedAsyncObjectContext = [self managedAsyncObjectContext];
    NSManagedObjectContext *rootObjectContext = [self rootObjectContext];
    
    if (nil == managedAsyncObjectContext) {
        return;
    }
    if ([managedAsyncObjectContext hasChanges]) {
        NSLog(@"Main context need to save");
        [managedAsyncObjectContext performBlockAndWait:^{
            NSError *error = nil;
            if (![managedAsyncObjectContext save:&error]) {
                NSLog(@"Save main context failed and error is %@", error);
            }
        }];
    }
    if (nil == rootObjectContext) {
        return;
    }
    if ([rootObjectContext hasChanges]) {
        NSLog(@"Root context need to save");
        if (needWait) {
            [rootObjectContext performBlockAndWait:^{
                NSError *error = nil;
                if (![_rootObjectContext save:&error]) {
                    NSLog(@"Save root context failed and error is %@", error);
                }
            }];
        }
        else {
            [rootObjectContext performBlock:^{
                NSError *error = nil;
                if (![_rootObjectContext save:&error]) {
                    NSLog(@"Save root context failed and error is %@", error);
                }
            }];
        }
    }
}
CoreData多线程工具类DBAsyncHelper
#import 
#import 
@interface DBAsyncHelper : NSObject

typedef void (^ DBCompletionBlock) (BOOL operationSuccess, id responseObject, NSString *errorMessage);

/*********************************************************
 函数名称:startWithTimeConsumingOperation
 函数描述:子线程处理耗时DB操作(使用本类必须调此方法发起DB操作)
 输入参数:operationBlock :数据处理代码块  completionBlock :成功回调(主要用于刷新UI)
 输出参数:无
 返回值:无
 **********************************************************/
+ (void)startWithTimeConsumingOperation:(void(^)())operationBlock completionBlock:(DBCompletionBlock)completionBlock;

/*********************************************************
 函数名称:asyncSearchBy
 函数描述:根据ModelName查询记录
 输入参数:ModelEmptyName :数据库表名
 输出参数:查询结果
 返回值:NSMutableArray
 **********************************************************/
+(NSMutableArray*)asyncSearchBy:(NSString*)ModelEmptyName;

/*********************************************************
 函数名称:asyncSearchWithPredicateByEntity
 函数描述:自定义二级查询
 输入参数:modelEntityName:Entity名  predicate:查询条件(NSPredicate对象)
 输出参数:查询结果
 返回值:NSMutableArray
 **********************************************************/
+(NSMutableArray*)asyncSearchWithPredicateByEntity:(NSString*)modelEntityName withKeys:(NSPredicate*) predicate;

/*********************************************************
 函数名称:asyncInsertWithEntity
 函数描述:根据Model名称获得一个数据库实例对象
 输入参数:ModelEmptyName:数据库表名
 输出参数:数据库实例对象
 返回值:id
 **********************************************************/
+(id)asyncInsertWithEntity:(NSString*)ModelEntityName;

+(void)asyncDeleteWithOutSaveBy:(id)obj;

+(Boolean)Save;

@end
DBAsyncHelper.m具体实现
#import "DBAsyncHelper.h"

#import "AppDelegate.h"
#import "TCFoundation.h"

@implementation DBAsyncHelper

+ (void)startWithTimeConsumingOperation:(void(^)())operationBlock completionBlock:(DBCompletionBlock)completionBlock
{
    NSManagedObjectContext *context = [AppDelegateEntity privateAsyncObjectContext];
    [context performBlock:^{
        operationBlock();
        NSError * error = nil;
        if ([context save:&error]) {
            [AppDelegateEntity saveAsyncContextWithWait:YES];
            completionBlock(YES,nil,nil);
        } else {
            NSString *sError = [NSString stringWithFormat:@"%@",error.localizedDescription];
            NSDebugLog(@"%@",sError);
            completionBlock(NO,nil,sError);
        }
    }];
}

+(NSMutableArray*)asyncSearchBy:(NSString*)ModelEmptyName
{
    NSManagedObjectContext* context = [AppDelegateEntity privateAsyncObjectContext];
    NSFetchRequest* request = [[NSFetchRequest alloc] init];
    NSEntityDescription* entityDesc = [NSEntityDescription entityForName:ModelEmptyName inManagedObjectContext:context];
    [request setEntity:entityDesc];
    
    NSError* error;
    NSArray* objects = [[NSArray alloc] initWithArray:[context executeFetchRequest:request error:&error]];
    NSMutableArray* mutableObjects = [[NSMutableArray alloc] initWithArray:objects];
    return mutableObjects;
}


+(NSMutableArray*)asyncSearchWithPredicateByEntity:(NSString*)modelEntityName withKeys:(NSPredicate*) predicate
{
    NSManagedObjectContext *context = [AppDelegateEntity privateAsyncObjectContext];
    NSFetchRequest* request = [[NSFetchRequest alloc] init];
    NSEntityDescription* entityDesc = [NSEntityDescription entityForName:modelEntityName inManagedObjectContext:context];
    [request setEntity:entityDesc];
    [request setPredicate:predicate];
    NSError* error;
    NSArray* objects = [[NSArray alloc] initWithArray:[context executeFetchRequest:request error:&error]];
    NSMutableArray *resultObjects = [[NSMutableArray alloc] initWithArray:objects];
    return resultObjects;
}

+(id)asyncInsertWithEntity:(NSString*)ModelEntityName
{
    NSManagedObjectContext *context = [AppDelegateEntity privateAsyncObjectContext];
    id obj = [NSEntityDescription insertNewObjectForEntityForName:ModelEntityName inManagedObjectContext:context];
    return obj;
}

+(void)asyncDeleteWithOutSaveBy:(id)obj
{
    NSManagedObjectContext *context = [AppDelegateEntity privateAsyncObjectContext];
    [context deleteObject:obj];
}

+(Boolean)Save
{
    NSManagedObjectContext *context = [AppDelegateEntity privateAsyncObjectContext];
    NSError* error = nil;
    if (![context save:&error])
    {
        return NO;
    }
    return YES;
}
DBAsyncHelper使用示例
//子线程更新站点
- (void)asyncUpdateStationList
{
    [DBAsyncHelper startWithTimeConsumingOperation:^{
     //使用DBAsyncHelper中相关增删方法进行数据库耗时操作
    } completionBlock:^(BOOL operationSuccess, id responseObject, NSString *errorMessage) {
        //handle result
    }];
}

备注

本文重点在于整理出一种可以直接使用的CoreData多线程解决方案,学习过程中参考了以下文章:
iOS CoreData详解(五)多线程
CoreData 多线程下NSManagedObjectContext的使用

你可能感兴趣的:(CoreData多线程实现初探(附源码))