美文网首页iOS Developer@IT·互联网程序员
CoreData多线程实现初探(附源码)

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

作者: dj_rose | 来源:发表于2017-06-16 16:02 被阅读182次

    由于历史的原因,项目中的火车票城市列表一直采用的全量更新,即:如果需要增删改某几个城市,则必须把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线程的。

    方案一.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 <Foundation/Foundation.h>
    #import <CoreData/CoreData.h>
    @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多线程实现初探(附源码)

        本文链接:https://www.haomeiwen.com/subject/pxxmqxtx.html