ios - iPad Mini 上 GCD 后台队列挂起时的 CoreData 保存

标签 ios ipad core-data grand-central-dispatch ipad-mini

我有一个单例类,它使用 GCD(Grand Central Dispatch)队列将 JSON 对象保存到后台的 CoreData 数据库中。这在大多数情况下都可以正常工作,但在 iPad 2 和 iPad Mini 设备上,我遇到了一些进程卡住问题。

我的设置非常简单。我有一个设置为串行运行的后台调度队列(backgroundQueue),并且我有一个用于后台队列的单独的 NSManagedObjectContext 实例。当我想将某些内容保存到数据库时,我调用开始保存过程的方法,并在该方法中使用dispatch_async在后台线程上调用我的保存逻辑。

一旦所有处理逻辑都运行完毕,我将保存后台 MOC,并使用 NSManagedObjectContextDidSaveNotification 将更改从后台 MOC 合并到主线程 MOC。后台线程等待此完成,一旦完成,它将运行队列中的下一个 block (如果有)。下面是我的代码。

dispatch_queue_t backgroundQueue;

- (id)init
{
    if (self = [super init]) 
    {
        // init the background queue
        backgroundQueue = dispatch_queue_create(VS_CORE_DATA_MANAGER_BACKGROUND_QUEUE_NAME, DISPATCH_QUEUE_SERIAL);
    }
    return self;
}

- (void)saveJsonObjects:(NSDictionary *)jsonDict
          objectMapping:(VS_ObjectMapping *)objectMapping
                  class:(__unsafe_unretained Class)managedObjectClass
             completion:(void (^)(NSArray *objects, NSError *error))completion
{
    [VS_Log logDebug:@"**** Queue Save JSON Objects for Class: %@", managedObjectClass];

    // process in the background queue
    dispatch_async(backgroundQueue, ^(void)
    {
        [VS_Log logDebug:@"**** Dispatch Save JSON Objects for Class: %@", managedObjectClass];

        // create a new process object and add it to the dictionary
        currentRequest = [[VS_CoreDataRequest alloc] init];

        // set the thead name
        NSThread *currentThread = [NSThread currentThread];
        [currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME];

        // if there is not already a background context, then create one
        if (!_backgroundQueueManagedObjectContext)
        {
            NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
            if (coordinator != nil)
            {
                // create the background queue
                _backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init];
                [_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator];
                [_backgroundQueueManagedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
                _backgroundQueueManagedObjectContext.undoManager = nil;

                // listen for the merge changes from context did save notification on the background queue
                [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFromBackground:) name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext];
            }
        }

        // save the JSON dictionary
        NSArray *objects = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0];

        // save the objects so we can access them later to be re-fetched and returned on the main thread
        if (objects.count > 0)
            [currentRequest.objects addObjectsFromArray:objects];

        // save the object IDs and the completion block to global variables so we can access them after the save
        if (completion)
            currentRequest.completionBlock = completion;

        [VS_Log logDebug:@"**** Save MOC for Class: %@", managedObjectClass];

        // save all changes object context
        [self saveManagedObjectContext];
    });
}

- (void)mergeChangesFromBackground:(NSNotification *)notification
{
    [VS_Log logDebug:@"**** Merge Changes From Background"];

    // save the current request to a local var since we're about to be processing it on the main thread
    __block VS_CoreDataRequest *request = (VS_CoreDataRequest *)[currentRequest copy];
    __block NSNotification *bNotification = (NSNotification *)[notification copy];

    // clear out the request
    currentRequest = nil;

    dispatch_sync(dispatch_get_main_queue(), ^(void)
    {
        [VS_Log logDebug:@"**** Start Merge Changes On Main Thread"];

        // merge changes to the primary context, and wait for the action to complete on the main thread
        [_managedObjectContext mergeChangesFromContextDidSaveNotification:bNotification];

        NSMutableArray *objects = [[NSMutableArray alloc] init];

        // iterate through the updated objects and find them in the main thread MOC
        for (NSManagedObject *object in request.objects)
        {
            NSError *error;
            NSManagedObject *obj = [[self managedObjectContext] existingObjectWithID:object.objectID error:&error];
            if (error)
                [self logError:error];

            if (obj)
                [objects addObject:obj];
        }

        // call the completion block
        if (request.completionBlock)
        {
            void (^saveCompletionBlock)(NSArray *, NSError *) = request.completionBlock;
            saveCompletionBlock(objects, nil);
        }

        [VS_Log logDebug:@"**** Complete Merge Changes On Main Thread"];

        // clear the request
        request = nil;
    });

    [VS_Log logDebug:@"**** Complete Merge Changes From Background"];
}

当问题发生时,一切似乎都运行良好,并且它进入 mergeChangesFromBackground: 方法,但没有调用dispatch_async()。

正如我上面提到的,代码在大多数情况下都能完美运行。只有当我在 iPad 2 或 iPad Mini 上进行测试并保存大型对象时,它才会出现运行问题。

有人知道为什么会发生这种情况吗?

谢谢

** 编辑 **

自从写这篇文章以来,我了解了嵌套的 NSManagedObjectContexts。我已经修改了我的代码来使用它,它似乎运行良好。但是,我仍然有同样的问题。想法?另外,对嵌套 MOC 有什么评论吗?

- (void)saveJsonObjects:(NSDictionary *)jsonDict
          objectMapping:(VS_ObjectMapping *)objectMapping
                  class:(__unsafe_unretained Class)managedObjectClass
             completion:(void (^)(NSArray *objects, NSError *error))completion
{
    // process in the background queue
    dispatch_async(backgroundQueue, ^(void)
    {
        // create a new process object and add it to the dictionary
        __block VS_CoreDataRequest *currentRequest = [[VS_CoreDataRequest alloc] init];

        // set the thead name
        NSThread *currentThread = [NSThread currentThread];
        [currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME];

        // if there is not already a background context, then create one
        if (!_backgroundQueueManagedObjectContext)
        {
            NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
            if (coordinator != nil)
            {
                // create the background queue
                _backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
                [_backgroundQueueManagedObjectContext setParentContext:[self managedObjectContext]];
                [_backgroundQueueManagedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
                _backgroundQueueManagedObjectContext.undoManager = nil;
            }
        }

        // save the JSON dictionary starting at the upper most level of the key path
        NSArray *objects = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0];

        // if no objects were processed, then return with an error
        if (!objects || objects.count == 0)
        {
            currentRequest = nil;

            // dispatch the completion block
            dispatch_async(dispatch_get_main_queue(), ^(void)
            {
                NSError *error = [self createErrorWithErrorCode:100 description:@"No objects were found for the object mapping"];
                [self logMessage:error.debugDescription];
                completion(nil, error);
            });
        }

        // save the objects so we can access them later to be re-fetched and returned on the main thread
        if (objects.count > 0)
            [currentRequest.objects addObjectsFromArray:objects];

        // save the object IDs and the completion block to global variables so we can access them after the save
        if (completion)
            currentRequest.completionBlock = completion;

        // save all changes object context
        NSError *error = nil;
        [_backgroundQueueManagedObjectContext save:&error];
        if (error)
            [VS_Log logError:error.localizedDescription];

        dispatch_sync(dispatch_get_main_queue(), ^(void)
        {
            NSMutableArray *objects = [[NSMutableArray alloc] init];

            // iterate through the updated objects and find them in the main thread MOC
            for (NSManagedObject *object in currentRequest.objects)
            {
                NSError *error;
                NSManagedObject *obj = [[self managedObjectContext] existingObjectWithID:object.objectID error:&error];
                if (error)
                    [self logError:error];

                if (obj)
                    [objects addObject:obj];
            }

            // call the completion block
            if (currentRequest.completionBlock)
            {
                void (^saveCompletionBlock)(NSArray *, NSError *) = currentRequest.completionBlock;
                saveCompletionBlock(objects, nil);
            }
        });

        // clear out the request
        currentRequest = nil;
    });
}

** 编辑 **

大家好,

首先我要感谢大家对此提出的意见,因为这使我找到了问题的解决方案。长话短说,我完全放弃了使用 GCD 和自定义后台队列,而是现在使用嵌套上下文和“performBlock”方法来执行后台线程上的所有保存。这非常有效,我提升了挂断问题。

但是,我现在遇到了一个新错误。每当我第一次运行我的应用程序时,每当我尝试保存具有子对象关系的对象时,我都会收到以下异常。

-_referenceData64 仅为抽象类定义。定义-[NSTemporaryObjectID_default _referenceData64]!

下面是我的新代码。

- (void)saveJsonObjects:(NSDictionary *)jsonDict
          objectMapping:(VS_ObjectMapping *)objectMapping
                  class:(__unsafe_unretained Class)managedObjectClass
             completion:(void (^)(NSArray *objects, NSError *error))completion
{
    [_backgroundManagedObjectContext performBlock:^(void)
    {
        // create a new process object and add it to the dictionary
        VS_CoreDataRequest *currentRequest = [[VS_CoreDataRequest alloc] init];
        currentRequest.managedObjectClass = managedObjectClass;

        // save the JSON dictionary starting at the upper most level of the key path
        NSArray *objects = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundManagedObjectContext level:0];

        if (objects.count == 0)
        {
            currentRequest.error = [self createErrorWithErrorCode:100 description:@"No objects were found for the object mapping"];
            [self performSelectorOnMainThread:@selector(backgroundSaveProcessDidFail:) withObject:currentRequest waitUntilDone:NO];
        }
        else
        {
            // save the objects so we can access them later to be re-fetched and returned on the main thread
            [currentRequest.objects addObjectsFromArray:objects];

            // save the object IDs and the completion block to global variables so we can access them after the save
            currentRequest.completionBlock = completion;

            [_backgroundManagedObjectContext lock];

            @try
            {
                [_backgroundManagedObjectContext processPendingChanges];
                [_backgroundManagedObjectContext save:nil];
            }
            @catch (NSException *exception)
            {
                currentRequest.error = [self createErrorWithErrorCode:100 description:exception.reason];
            }

            [_backgroundManagedObjectContext unlock];

            // complete the process on the main thread
            if (currentRequest.error)
                [self performSelectorOnMainThread:@selector(backgroundSaveProcessDidFail:) withObject:currentRequest waitUntilDone:NO];
            else
                [self performSelectorOnMainThread:@selector(backgroundSaveProcessDidSucceed:) withObject:currentRequest waitUntilDone:NO];

            // clear out the request
            currentRequest = nil;
        }
    }];
}

- (void)backgroundSaveProcessDidFail:(VS_CoreDataRequest *)request
{
    if (request.error)
        [self logError:request.error];

    if (request.completionBlock)
    {
        void (^saveCompletionBlock)(NSArray *, NSError *) = request.completionBlock;
        saveCompletionBlock(nil, request.error);
    }
}

- (void)backgroundSaveProcessDidSucceed:(VS_CoreDataRequest *)request
{
    // get objects from main thread
    NSArray *objects = nil;
    if (request.objects.count > 0)
    {
        NSFetchRequest *fetchReq = [NSFetchRequest fetchRequestWithEntityName:[NSString stringWithFormat:@"%@", request.managedObjectClass]];
        fetchReq.predicate = [NSPredicate predicateWithFormat:@"self IN %@", request.objects];
        objects = [self executeFetchRequest:fetchReq];
    }

    // call the completion block
    if (request.completionBlock)
    {
        void (^saveCompletionBlock)(NSArray *, NSError *) = request.completionBlock;
        saveCompletionBlock(objects, nil);
    }
}

- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil)
    {
        // create the MOC for the backgroumd thread
        _backgroundManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_backgroundManagedObjectContext setPersistentStoreCoordinator:coordinator];
        [_backgroundManagedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
        _backgroundManagedObjectContext.undoManager = nil;

        // create the MOC for the main thread
        _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [_managedObjectContext setParentContext:_backgroundManagedObjectContext];
        [_managedObjectContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
    }

    return _managedObjectContext;
}

你们中有人知道为什么会发生这次崩溃吗?

最佳答案

您可能需要考虑这种上下文架构(未经测试的代码):
mainManagedObjectContext 将以相同的方式初始化,但使用 NSMainQueueConcurrencyType 且没有观察者(顺便说一句,请记住删除观察者)

- (void) mergeChangesFromBackground:(NSNotification*)notification
{
    __block __weak NSManagedObjectContext* context = self.managedObjectContext;
    [context performBlockAndWait:^{
        [context mergeChangesFromContextDidSaveNotification:notification];
    }];
}

- (NSManagedObjectContext*) bgContext
{
    if (_bgContext) {
        return _bgContext;
    }

    NSPersistentStoreCoordinator* coordinator = [self persistentStoreCoordinator];
    if (!coordinator) {
        return nil;
    }
    _bgContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [_bgContext setPersistentStoreCoordinator:coordinator];
    [_bgContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
    [_bgContext setUndoManager:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(mergeChangesFromBackground:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:_bgContext];
    return _bgContext;
}

- (void) doSomethingOnBGContextWithoutBlocking:(void(^)(NSManagedObjectContext*))contextBlock
{
    __block __weak NSManagedObjectContext* context = [self bgContext];
    [context performBlock:^{
        NSThread *currentThread = [NSThread currentThread];
        NSString* prevName = [currentThread name];
        [currentThread setName:@"BGContextThread"];

        contextBlock(context);

        [context reset];
        [currentThread setName:prevName];
    }];
}

关于ios - iPad Mini 上 GCD 后台队列挂起时的 CoreData 保存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15820629/

相关文章:

iphone - NSManagedObject 子类,具有模型中未定义的附加属性

iphone - 如何提高自定义 OpenGL ES 2.0 深度纹理生成的性能?

ios - 在 UINavigationBar 左侧的 "back"按钮旁边添加另一个按钮

使用 CoreData 插入数千条数据时 iOS 应用程序崩溃

ios - NSPredicate/TableView 只显示一项,为什么?

ios - StatusBarStyle 在 ios 13.5 上不会改变

ios - 如何检索 Xcode 中丢弃的更改?

php - 从 JSON post 到 PHP 将数据输入数据库时​​,mysql 中出现空行

iphone - iOS 开发 : How can I induce low memory warnings on device?

ios - 为什么核心数据不会持久保存到磁盘?