从异步 Web 服务响应更新托管对象的最佳方法?
Posted
技术标签:
【中文标题】从异步 Web 服务响应更新托管对象的最佳方法?【英文标题】:Best approach to update managed objects from async web services responses? 【发布时间】:2015-09-27 07:50:12 【问题描述】:我有一个与主线程 (mainContext
) 关联的 NSManagedObjectContext
,我在其中获取我在整个应用程序中显示的所有 NSManagedObject
。
用户不编辑这些对象,但我从 Web 服务获取更新。我定期对此类服务执行异步调用,它们“告诉”我必须删除哪些托管对象(如果有),哪些必须用新信息更新(如果有),以及是否需要插入新对象。
所以,我需要首先获取所有服务的响应,然后检查我必须对我的mainContext
中已有的托管对象进行哪些更改。而且我还需要执行更新以避免阻塞 UI。
我正在考虑两种方法来管理这种情况:
-
在具有自己的核心数据堆栈的私有队列中使用完全分离的
privateContext
来插入我从服务中获得的所有对象。然后以某种方式(如何?)与我在mainContext
中的对象进行比较,并删除/修改/插入mainContext
中的对象。
在专用队列中使用privateContext
,但作为mainContext
的子级。然后我需要将我在其父 mainContext
中的对象传递给子上下文(这可能吗?如何?),同时在这个子上下文中插入我从服务中获得的对象,然后比较和执行更改。
哪种方法是最好的或合适的?或者也许它应该是一个我没有想过的不同的?
提前致谢
编辑:这可能是另一种可能的方式吗?:
-
仅使用
mainContext
并且,当我解析服务的响应时,而不是创建新对象,只需对 mainContext
进行一一更改...
编辑 2: 另一种可能性?:
-
仅使用
privateContext
,获取服务响应并创建新对象。然后,还用这个privateContext
获取所有已经存在的对象(这将与mainContext
中的对象相同)。在此 privateContext
中进行更改,比较两组对象(最近从服务创建的对象和获取的对象),保存此上下文,清除 mainContext
并重新获取 mainContext
中的所有对象。
【问题讨论】:
你的对象没有任何 ID 属性吗? @kirander 是的,他们有 将私有上下文与父上下文一起使用。通过从服务器响应中获取的 id 从存储中获取所需的对象。根据情况更新、删除或创建新的。 @kirander 那将是我刚刚在我的问题中编辑的选项 4?关键是,我在尝试将mainContext
设置为privateContext
的父级时遇到了错误,我不知道为什么......该选项不能通过使用单独的privateContext
来工作吗?
这是 swift 还是 Obj-C?
【参考方案1】:
我不确定这是否是您的完整答案,但我正在处理类似的情况。我采取的实现路径是使用子对象(在所有地方) - 或者更像是临时子上下文的需要。
不过,我首先要提到的是确保使用 XCOde 中内置的 CoreData 调试功能。我做了第二个运行方案,它有
-com.apple.CoreData.ConcurrencyDebug 1
在应用程序初始化时,我有正常的NSManagedObjectContext
- 它被传递到我的后台网络线程。
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[child setParentContext:parentObjectContext];
然后,每当我需要将某些东西从父母传递给孩子时,我最终都会这样做:
[child objectWithID:(object-in-parent-context)]
或者我最终会这样做
__block AHRS_RPYL * ret;
[[self getChildContext] performBlockAndWait:^
ret = [NSEntityDescription insertNewObjectForEntityForName:@"RPYL" inManagedObjectContext:[self getChildContext]];
// ... lots of code
];
我不能说我真的“喜欢”这种方法,我现在有点坚持它,但它似乎确实运作良好。
在我的大多数视图控制器之间我都有一个
@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
都不是对线程最友好的选项。
UDPATES
保存代码
我实际上每 50 条消息保存到子上下文,每 250 条消息保存到父上下文(好久没碰过这段代码了)。我不能保证这是做事的最佳正确方法,但它确实有效,并且确实将磁盘访问降至最低。我收到很多消息,比如每秒 20 多条,所以我想做这种堆栈类型。你可能不在乎
(self.getChild()) 返回子上下文 - 我留下的旧代码。
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();
];
];
...
再次 - 就像我之前说的,这可能不是最好的做事方式,但我确实有一个父/子堆栈,它确实有效。这是我编写的第一个 ios 应用程序之一,如果要重做,我会使用核心数据并使用其他东西。
我们的更新率非常高,所以当我们使用单个堆栈时,它会使 UI 变得非常慢。向父母/孩子的迁移很慢 - 如果我再做一次,我认为它会变得更顺利,因为我会编写许多辅助函数来处理其中的一些(或者只是使用 swift)。
祝你好运。
【讨论】:
感谢您的解释。我猜应该在子上下文的performBlockAndWait:
中调用[child objectWithID:(object-in-parent-context)]
,对吧?
关于这种方法:当您从子上下文中删除从其父级传递的对象时,当子级保存时它也从父级中删除?在子上下文中插入的相同问题。我需要在执行更新时进行删除和插入,而不仅仅是现有对象的更改......再次感谢以上是关于从异步 Web 服务响应更新托管对象的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章