从异步 Web 服务响应更新托管对象的最佳方法?
Best approach to update managed objects from async web services responses?
我有一个与主线程 (mainContext
) 关联的 NSManagedObjectContext
,我在其中获取我在整个应用程序中显示的所有 NSManagedObject
。
用户不编辑这些对象,但我从网络服务获得更新。我定期对此类服务执行异步调用,它们 "tell" 我必须删除哪些托管对象(如果有的话),哪些必须用新信息更新(如果有的话),以及我是否需要插入新对象。
因此,我需要首先获取所有服务的响应,然后检查我必须对 mainContext
中已有的托管对象进行哪些更改。而且我还需要执行更新以避免阻塞 UI.
我正在考虑 2 种方法来管理这种情况:
- 在私有队列中使用一个完全独立的
privateContext
,它有自己的核心数据堆栈,将我从服务中获得的所有对象插入到那里。然后以某种方式(如何?)与 mainContext
中的对象和 mainContext
. 中的 delete/modify/insert 对象进行比较
- 在专用队列中使用
privateContext
,但它是 mainContext
的子队列。然后我需要将我在其父 mainContext
中拥有的对象传递给子上下文(这可能吗?如何?),同时在此子上下文中插入我从服务中获取的对象,然后比较并执行更改。
哪种方法最好或合适?或者也许它应该是一个我没有想过的不同的人?
提前致谢
编辑:这可能是另一种可能的方式吗?:
- 只使用
mainContext
并且,因为我正在解析服务的响应,而不是创建新对象,只是对 mainContext
一个一个地进行更改...
编辑 2: 另一种可能性?:
- 仅使用
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)。
祝你好运。
我有一个与主线程 (mainContext
) 关联的 NSManagedObjectContext
,我在其中获取我在整个应用程序中显示的所有 NSManagedObject
。
用户不编辑这些对象,但我从网络服务获得更新。我定期对此类服务执行异步调用,它们 "tell" 我必须删除哪些托管对象(如果有的话),哪些必须用新信息更新(如果有的话),以及我是否需要插入新对象。
因此,我需要首先获取所有服务的响应,然后检查我必须对 mainContext
中已有的托管对象进行哪些更改。而且我还需要执行更新以避免阻塞 UI.
我正在考虑 2 种方法来管理这种情况:
- 在私有队列中使用一个完全独立的
privateContext
,它有自己的核心数据堆栈,将我从服务中获得的所有对象插入到那里。然后以某种方式(如何?)与mainContext
中的对象和mainContext
. 中的 delete/modify/insert 对象进行比较
- 在专用队列中使用
privateContext
,但它是mainContext
的子队列。然后我需要将我在其父mainContext
中拥有的对象传递给子上下文(这可能吗?如何?),同时在此子上下文中插入我从服务中获取的对象,然后比较并执行更改。
哪种方法最好或合适?或者也许它应该是一个我没有想过的不同的人?
提前致谢
编辑:这可能是另一种可能的方式吗?:
- 只使用
mainContext
并且,因为我正在解析服务的响应,而不是创建新对象,只是对mainContext
一个一个地进行更改...
编辑 2: 另一种可能性?:
- 仅使用
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)。
祝你好运。