从异步 Web 服务响应更新托管对象的最佳方法?

Best approach to update managed objects from async web services responses?

我有一个与主线程 (mainContext) 关联的 NSManagedObjectContext,我在其中获取我在整个应用程序中显示的所有 NSManagedObject

用户不编辑这些对象,但我从网络服务获得更新。我定期对此类服务执行异步调用,它们 "tell" 我必须删除哪些托管对象(如果有的话),哪些必须用新信息更新(如果有的话),以及我是否需要插入新对象。

因此,我需要首先获取所有服务的响应,然后检查我必须对 mainContext 中已有的托管对象进行哪些更改。而且我还需要执行更新以避免阻塞 UI.

我正在考虑 2 种方法来管理这种情况:

  1. 在私有队列中使用一个完全独立的 privateContext,它有自己的核心数据堆栈,将我从服务中获得的所有对象插入到那里。然后以某种方式(如何?)与 mainContext 中的对象和 mainContext.
  2. 中的 delete/modify/insert 对象进行比较
  3. 在专用队列中使用 privateContext,但它是 mainContext 的子队列。然后我需要将我在其父 mainContext 中拥有的对象传递给子上下文(这可能吗?如何?),同时在此子上下文中插入我从服务中获取的对象,然后比较并执行更改。

哪种方法最好或合适?或者也许它应该是一个我没有想过的不同的人?

提前致谢

编辑:这可能是另一种可能的方式吗?:

  1. 只使用 mainContext 并且,因为我正在解析服务的响应,而不是创建新对象,只是对 mainContext 一个一个地进行更改...

编辑 2: 另一种可能性?:

  1. 仅使用 privateContext,获取服务响应并创建新对象。然后,还使用此 privateContext 获取所有已存在的对象(这与 mainContext 中的对象相同)。在此 privateContext 中进行更改,比较两组对象(最近从服务创建的和获取的),保存此上下文,清除 mainContext 并重新获取 mainContext 中的所有对象。

我不确定这是否是您的完整答案,但我正在处理类似的情况。我采取的实现路径是使用 child objects(到处都是)——或者更像是根据需要使用临时 child 上下文。

不过,我要提到的第一件事是确保使用 XCOde 中内置的 CoreData 调试功能。我做了第二个 Run-Scheme

-com.apple.CoreData.ConcurrencyDebug 1

在应用程序初始化时,我有正常的 NSManagedObjectContext - 它被传递到我的后台网络线程。

NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
   [child setParentContext:parentObjectContext];

然后每当我需要将某些东西从 parent 传递到 child 时,我最终会做:

[child objectWithID:(object-in-parent-context)]

或者我最终会做

__block AHRS_RPYL * ret;

[[self getChildContext] performBlockAndWait:^{

    ret = [NSEntityDescription insertNewObjectForEntityForName:@"RPYL" inManagedObjectContext:[self getChildContext]];

// ... lots of code
}];

我不能说我真的 "love" 这种方法,目前我有点坚持使用它,但它似乎工作得很好。

在我的大多数视图控制器之间,我有一个

@synthesize managedObjectContext;

在我的prepareForSegue方法中

// Pass on managedObjectContext
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

    //    dispatch_async(dispatch_get_main_queue(), ^{
    // If the destination VC is able to take teh setManagedObjectContext method the current objectContext will be passed along.
    if ([segue.destinationViewController respondsToSelector:@selector(setManagedObjectContext:)]) {
        [segue.destinationViewController performSelector:@selector(setManagedObjectContext:)
                                              withObject:self.managedObjectContext];

    } else {

        NSLog(@"Segue to controller [%@] that does not support passing managedObjectContext", [segue destinationViewController]);
    }
    //    });
}

总结

  • 我已经使用了你的第二个选项,它是可行的,尽管在我看来它有点笨拙。事实上,我们正在考虑切换到 Realm 而不是 CoreData,因为无论您怎么看,CoreData 都不是对线程最友好的选项。

更新

保存代码

我实际上每 50 条消息保存到 child 上下文,每 250 条消息保存到 parent 看起来像(很久没有触及此代码)。我不能保证这是做事的最佳正确方法,但它确实有效,并且确实将光盘访问保持在最低限度。我收到很多消息,比如每秒 20 多条,所以我想做这种堆栈类型。你可能不在乎

(self.getChild()) returns child 上下文 - 它的旧代码我留在了这里。

if (msgCount % 50 == 0) {
                // Child Save!

                //                    NSLog(@"Saving SDatas");
                __block NSManagedObjectContext *currentChild = [self getChildContext];
                [self incChildContext];

                // Parent-Child save methodology
                [currentChild performBlock:^{
                    NSError *error;
                    if (![currentChild save:&error]) {
                        NSLog(@"\n error => %@ \n", [error localizedDescription]);
                        NSLog(@" error => %@ ", [error userInfo]);
                        [NSException raise:@"Database Write Error" format:@"%@ %@", [error localizedDescription], [error userInfo]];

                        //                           abort();
                    }

                    if (msgCount % 250 < 5) {

                        [parentObjectContext performBlock:^{
                            NSError *error;
                            if (![parentObjectContext save:&error]) {
                                NSLog(@"\n error => %@ \n", [error localizedDescription]);
                                NSLog(@" error => %@ ", [error userInfo]);
                                [NSException raise:@"Database Write Error" format:@"%@ %@", [error localizedDescription], [error userInfo]];

                                //                                   abort();


                            }
                        }];
                    }

                    [currentChild reset];
                }];


            }

正在删除

 [childContext performBlock:^{

 // LOTS OF CODE
 // to build a query set of the records we want to kill
// End lots of code
[childContext deleteObject:msg];


        if (i % modFactor == 0) {
            self.percentDone = i / totalRecords;

            NSLog(@"%.1f Saving ...", self.percentDone * 100);


            NSError *error;
            if (![childContext save:&error]) {
                NSLog(@"\n error => %@ \n", [error localizedDescription]);
                NSLog(@" error => %@ ", [error userInfo]);
                [NSException raise:@"Database Write Error" format:@"%@ %@", [error localizedDescription], [error userInfo]];

                //                    abort();
            }

            [parentContext performBlock:^{
                NSError *errrror;
                if (![parentContext save:&errrror]) {
                    NSLog(@"\n error => %@ \n", [error localizedDescription]);
                    NSLog(@" error => %@ ", [error userInfo]);
                    [NSException raise:@"Database Write Error" format:@"%@ %@", [error localizedDescription], [error userInfo]];

                    //                        abort();
                }
            }];
        }

}];  

...

再次 - 就像我之前说的,这可能不是最好的做事方式,但我确实有一个 parent/child 堆栈并且它确实有效。这是我编写的第一个 iOS 应用程序之一,为了重新编写它,我将赌注放在核心数据上并使用其他东西。

我们的更新率非常高,所以当我们使用单个堆栈时,它会使 UI 非常慢。迁移到 parent/child 很慢 - 如果我再做一次,我认为它会变得更顺利,因为我会编写许多辅助函数来处理其中的一些(或者只使用 swift)。

祝你好运。