15

我正在从网络服务器获取数据,并在名为backgroundMOC. mainMOC它是链接到主 UI的 a 的子级,因此保存backgroundMOC触发器 UI 更改。mainMOC是 a 的子级,它masterMOC是绑定到持久存储的私有后台队列,因此保存在 master 上会保存到磁盘。

我现在做的是接收数据,在 上创建新对象backgroundMOC,然后保存backgroundMOC(以便 UI 更新),保存mainMOC,(以便我几乎可以保存到磁盘),然后保存masterMOC(以便我最终可以写入磁盘)。问题是当对象通过获取的结果控制器出现在 UI 中时,objectId它仍然是一个临时对象。

这会导致重复行问题的问题,如果我从服务器收到相同的数据(偶然),我backgroundMOC不知道这个对象已经存在,因为它没有被分配一个永久 id,所以它创建了另一个对象。当我重新启动应用程序时,重复的对象消失了,所以我知道这只是 id 映射的问题。

所以我想我可以试试

[backgroundMOC obtainPermanentIDsForObjects:backgroundMOC.registeredObjects.allObjects error:nil];

在完全保存之前(我也在保存后尝试过)。但是,由于某种原因,调用此行会引发异常:

CoreData 无法满足...

如果您有任何提示可以引导我朝着正确的方向前进,请分享。谢谢

编辑:好的,所以最初我在 backgroundMOC 上调用了 gainPermanentIDsForObjects,它是 mainMOC 的子对象,它是 masterMOC 的子对象。我切换它以便获得 mainMOC 上的 id,它解决了我所有的问题(目前)。我不应该在子上下文中调用 gainPermIds 吗?

4

2 回答 2

19

这是一个已知的错误(保存新对象时嵌套上下文没有获得永久 ID)可能,并且应该在即将发布的版本中修复......

虽然您应该能够要求永久 ID,但您应该只在已插入的对象上要求它们。

[moc obtainPermanentIDsForObjects:moc.insertedObjects.allObjects error:0];

但是,您必须在保存 MOC 之前执行此操作,因为如果您在没有获得永久 ID 的情况下保存,临时 ID 会传播到父上下文。例如,在您保存到 mainMoc,然后获取 IDS 的情况下,backgroundMOC 仍然具有临时 ID,因此将来从中保存将创建重复数据。

请注意,获取永久 ID 一直到数据库,但如果您在主 MOC 的子 MOC 中执行此操作,则在发生这种情况时您根本不应该阻塞主线程。

所以,在你从最低级别的 MOC 中保存时,你应该有效地拥有这样的东西(当然,有适当的错误处理)......

[backgroundMoc performBlock:^{
    [backgroundMoc obtainPermanentIDsForObjects:backgroundMoc.insertedObjects.allObjects error:0];
    [backgroundMoc save:0];
    [mainMoc performBlock:^{
       [mainMoc save:0];
        [masterMoc performBlock:^{
            [masterMoc save:0];
        }];
    }];
}];

如果您愿意,还可以玩一些其他游戏。

在 NSManagedObject 上提供与此类似的类别...

@implementation NSManagedObject (initWithPermanentID)
- (id)initWithEntity:(NSEntityDescription *)entity insertWithPermanentIDIntoManagedObjectContext:(NSManagedObjectContext *)context {
    if (self = [self initWithEntity:entity insertIntoManagedObjectContext:context]) {
        NSError *error = nil;
        if (![context obtainPermanentIDsForObjects:@[self] error:&error]) {
            @throw [NSException exceptionWithName:@"CoreData Error" reason:error.localizedDescription userInfo:error.userInfo];
        }
    }
    return self;
}

+ (NSArray*)createMultipleObjects:(NSUInteger)count withEntity:(NSEntityDescription *)entity inManagedObjectContext:(NSManagedObjectContext *)context {
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
    for (NSUInteger i = 0; i < count; ++i) {
        [array addObject:[[self alloc] initWithEntity:entity insertIntoManagedObjectContext:context]];
    }
    NSError *error = nil;
    if (![context obtainPermanentIDsForObjects:array error:&error]) {
        @throw [NSException exceptionWithName:@"CoreData Error" reason:error.localizedDescription userInfo:error.userInfo];
    }
    return array;
}
@end

现在,在第一个中,您需要付费进入数据库并为创建的每个实体创建一个 ID,但这并不多,而且它发生在后台线程中,并且每次下降都很短......

哦,好吧,它不是最好的,但它提供了有用的。此外,第二个创建多个相同对象,并同时获取它们的永久 ID。

您还可以使用直接连接到 PSC 的 MOC,并监视 DidChange 事件,但这与旧方法相同。

不幸的是,你不能有一个单独的 MOC 只发出 persistentID 请求并传递 ObjectID,尽管你可以有一个单独的 MOC 在 DB 中制作原型对象,并为你提供它们的 ObjectID。

原型工厂是一种相当常见的模式,如果你走这条路,当最终的错误修复到这里时,很容易做出微小的改变。

编辑

回应斯文...

如果您要创建新的复杂图表,则需要在创建后立即获取永久 ID。要减少对商店的点击次数,您应该全部创建它们,然后立即获取 ID,然后开始连接它们。

老实说,所有这些都是为了解决当前存在的错误,这些错误对于中小型更新来说是值得解决的。修复错误后,您的代码将是相同的(无需获取)。因此,我建议将这种方法用于较小的进口。

如果您正在进行大规模更新,我建议使用“旧”方法。创建一个直接连接到 PSC 的新 MOC。在那里进行所有更改,并让您的“实时”上下文从那些 DidSave 通知中合并。

最后,关于永久 ID 对数据库的影响。可以丢弃 MOC。磁盘被击中,元数据被改变,但对象没有被持久化。

老实说,我没有做一个大的测试来看看是否有任何空白空间,所以你可能想这样做,然后和我一起回来。

查看磁盘上实际的数据库文件大小,然后创建 10000 个对象,然后获取持久 ID,释放 MOC,再查看大小。

如果有影响,您可以尝试删除对象,或在大更新后对数据库运行清理以查看是否有效。

如果您要创建大量可能会丢弃的对象,则无需访问数据库。您可能只想直接附加到 PSC 并使用旧的忠实通知。

于 2012-08-16T22:15:15.870 回答
1

在前台和后台线程之间工作时,我对 Core Data 有过各种挫败感。在为我遇到的一个问题寻找解决方案时

魔法唱片

我花了一些时间阅读文档和方法,我可以说它确实使使用 Core Data 变得更加容易。具体来说,它还将帮助您管理多个上下文和线程。

你可能想检查一下。

于 2012-08-16T13:52:33.740 回答