使用NSFetchedResultsController

时间:2016-06-13 08:48:00

标签: ios objective-c multithreading core-data nsmanagedobjectcontext

这是一个非常奇怪的问题,我认为我理解Core Data。

我使用没有父级的背景上下文。直接进入持久性商店协调员。我在此背景上下文中更新对象,然后保存它。我听ContextDidSaveNotification并将这些更改合并到我的主线程上下文中。这些更新的对象不是主线程上的错误,因为它们已经用于填充表视图单元格。所以我希望这些更改能够实际合并。但他们不是。

如果不了解我的数据模型的细节,就可以说对象具有属性“downloadState”。解析工作在后台线程完成后,downloadStateValue(枚举)设置为“3”,对应于“已完成”。

我现在订阅了ContentWillSave通知来检查发生了什么。我在解析工作结束时得到了这个:

2016-06-13 10:19:21.055 MyApp[29162:52855206] Going to save background context.
updated:{(
<QLUserPinnedCourse: 0x7fe195403c10> (entity: QLUserPinnedCourse; id: 0xd0000000002c0002 <x-coredata://95821ADC-8A1F-4DAC-B20C-EDD8F8F413EA/QLUserPinnedCourse/p11> ; data: {
  course = "0xd000000000dc0008 <x-coredata://95821ADC-8A1F-4DAC-B20C-EDD8F8F413EA/QLCourse/p55>";
  courseId = 2794;
  /* other fields redacted */
}),
<QLCourse: 0x7fe1954cded0> (entity: QLCourse; id: 0xd000000000dc0008 <x-coredata://95821ADC-8A1F-4DAC-B20C-EDD8F8F413EA/QLCourse/p55> ; data: {
 /* other fields redacted*/
 contentDownloadState = 3;
 courseId = 2794;
 pinnedUserData = "0xd0000000002c0002 <x-coredata://95821ADC-8A1F-4DAC-B20C-EDD8F8F413EA/QLUserPinnedCourse/p11>";
    })

持有NSFetchedResultsController个对象的QLUserPinnedCourse获得delegate次调用,这会在我的表中触发单元格重新加载。

谓词是:

// Specify criteria for filtering which objects to fetch
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"pinned == %@ && course.contentDownloadState IN %@",
                                  @YES,
                                  @[@(QLDownloadStateSucceeded), @(QLDownloadStateNotYetAttempted), @(QLDownloadStateFailed), @(QLDownloadStateIncomplete)]
                                  ];

现在,当我到达单元代码时,我有一个QLUserPinnedCourse对象可以使用。我在调试器中设置了一个断点并得到:

(lldb) po userCourse.course
<QLCourse: 0x7fe19568f740> (entity: QLCourse; id: 0xd000000000dc0008 <x-coredata://95821ADC-8A1F-4DAC-B20C-EDD8F8F413EA/QLCourse/p55> ; data: {

    contentDownloadState = 1;
    courseId = 2794;
    pinnedUserData = "0xd0000000002c0002 <x-coredata://95821ADC-8A1F-4DAC-B20C-EDD8F8F413EA/QLUserPinnedCourse/p11>";
})

问题是,为什么contentDownloadState不是3,但仍然是1?我不明白。

这些变化不应该合并吗?

关于我的堆栈的详细信息:

PSC -> Private Concurrent (saving context) -> Main Thread context

PSC -> Private Concurrent (import context)

ContextDidSave: 如果上下文是导入上下文,则将更改合并到上面的两个上下文中:

_contextSaveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification
                                                                             object:nil
                                                                              queue:nil
                                                                         usingBlock:^(NSNotification* note)
                            {
                                NSManagedObjectContext *contextSaved = note.object;
                                NSManagedObjectContext *moc = weakself.mainQueueContext;

                                // basically, if this was a background worker thread

                                DDLogDebug(@"updatedObjects:%@", note.userInfo[NSUpdatedObjectsKey]);

                                if ([contextSaved.userInfo[CoreDataUserInfoKeyIsWorkerContext] boolValue])
                                {
                                    [weakself.privateSavingContext performBlock:^(){

                                        for (NSManagedObject *object in note.userInfo[NSUpdatedObjectsKey]) {
                                            [[weakself.privateSavingContext objectWithID:[object objectID]] willAccessValueForKey:nil];
                                        }

                                        [weakself.privateSavingContext mergeChangesFromContextDidSaveNotification:note];

                                        [moc performBlock:^(){

                                            for (NSManagedObject *object in note.userInfo[NSUpdatedObjectsKey]) {
                                                [[moc objectWithID:[object objectID]] willAccessValueForKey:nil];
                                            }

                                            [moc mergeChangesFromContextDidSaveNotification:note];


                                        }];
                                    }];
                                }
                            }];

请注意,我问userCourse。course对象的属性,尽管我的FRC对QLUserPinnedCourse个对象感兴趣。我想因为我在谓词中指定了与QLCourse对象相关的键路径,所以这些更改会被刷新。

2 个答案:

答案 0 :(得分:1)

这是核心数据的一个怪癖。实际上,您需要在主上下文中重新设置由保存操作更新的对象。

这是Swift中的一个例子:

mainContext.performBlock {
    let updatedObjects : Set<NSManagedObject> = notification.userInfo![NSUpdatedObjectsKey] as! Set<NSManagedObject>

    for obj in updatedObjects {
        self.mainContext.objectWithID(obj.objectID).willAccessValueForKey(nil)
    }

    self.mainContext.mergeChangesFromContextDidSaveNotification(notification)
}

主要部分是对willAccessValueForKey:nil的调用,导致该对象被标记为错误。这将导致主要上下文中的NSFetchedResultsController被触发。

答案 1 :(得分:1)

所以我找到了一个解决方案,但我无法告诉你为什么会有效。

我想问的是我会有一个方法'开始下载内容',我会将主线程上下文中的属性contentDownloadState更新为'不完整/下载',然后继续获取所有内容。

所有其余的工作都是在后台线程上下文中完成的。完成后,我用'succeeded'更新了这个值。它并没有合并这种变化。我不明白为什么。

一旦我决定在工作环境中做所有事情。即更改其值然后将上下文保存到磁盘,更改,所有更改都在传播。

所以最后我解决了它,但实际上并不理解这个问题。