Restkit加载的嵌套Core Data实体导致NSObjectInaccessibleException

发布于 2024-12-26 07:37:43 字数 2234 浏览 0 评论 0原文

我使用 RestKit 从 RoR 服务中获取对象,并使用 CoreData 来保存一些对象(更多静态类型的查找表对象)。 TasteTag 是那些持久化对象之一:

#ifdef RESTKIT_GENERATE_SEED_DB
    NSString *seedDatabaseName = nil;
    NSString *databaseName = RKDefaultSeedDatabaseFileName;
#else
    NSString *seedDatabaseName = RKDefaultSeedDatabaseFileName;
    NSString *databaseName = @"Model.sqlite";
#endif

RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:kServerURL];  
manager.objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:databaseName usingSeedDatabaseName:seedDatabaseName managedObjectModel:nil delegate:self];

.. lots of fun object mapping ..

 RKManagedObjectMapping* tasteTagMapping = [RKManagedObjectMapping mappingForClass:[TasteTag class]];
[tasteTagMapping mapKeyPath:@"id" toAttribute:@"tasteTagID"];
[tasteTagMapping mapKeyPath:@"name" toAttribute:@"name"];
tasteTagMapping.primaryKeyAttribute = @"tasteTagID";
[[RKObjectManager sharedManager].mappingProvider setMapping:tasteTagMapping forKeyPath:@"taste_tags"]; 
[[RKObjectManager sharedManager].mappingProvider addObjectMapping:tasteTagMapping];

.. some more mapping ..

我有从 RoR 服务器返回的数据,并且它按预期映射到对象。在 RestKit 收到请求后,核心数据实体似乎也映射得很好:

"<TasteTag: 0x6e87170> (entity: TasteTag; id: 0x6e85d60 <x-coredata://03E4A20A-21F2-4A2D-92B4-C4424893D559/TasteTag/p5> ; data: <fault>)"

问题是当我尝试访问对象的属性时,错误似乎无法被触发。起初我只是调用属性,它总是返回为零(即使这应该引发故障):

for (TasteTag *tag in self.vintage.tasteTags) {
    [tagNames addObject:tag.name]; //get error of trying to add nil to array   
}

在研究手动触发故障之后(http://www.mlsite.net/blog/?p=518)我尝试调用 [tag willAccessValueForKey:nil] 结果是:

Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x6e7b060 <x-coredata://03E4A20A-21F2-4A2D-92B4-C4424893D559/TasteTag/p5>''

查找实体在 .sqlite 中基于密钥(TasteTag/p5)确实显示它映射到我期望的那个。

与 RestKit 相关的其他帖子建议禁用对象缓存(我没有使用),因为这通常是由删除实体引起的。但在这个阶段我只是读取,而不是删除,而且我没有缓存。

如果我只是调用 [TasteTag allObjects] 我就能很好地恢复所有对象,并且它们可以毫无问题地加载。似乎只是在他们犯错的情况下。

I'm using RestKit to grab objects from my RoR service and using CoreData to persist some of the objects (more static-type lookup table objects). TasteTag is one of those persisted objects:

#ifdef RESTKIT_GENERATE_SEED_DB
    NSString *seedDatabaseName = nil;
    NSString *databaseName = RKDefaultSeedDatabaseFileName;
#else
    NSString *seedDatabaseName = RKDefaultSeedDatabaseFileName;
    NSString *databaseName = @"Model.sqlite";
#endif

RKObjectManager* manager = [RKObjectManager objectManagerWithBaseURL:kServerURL];  
manager.objectStore = [RKManagedObjectStore objectStoreWithStoreFilename:databaseName usingSeedDatabaseName:seedDatabaseName managedObjectModel:nil delegate:self];

.. lots of fun object mapping ..

 RKManagedObjectMapping* tasteTagMapping = [RKManagedObjectMapping mappingForClass:[TasteTag class]];
[tasteTagMapping mapKeyPath:@"id" toAttribute:@"tasteTagID"];
[tasteTagMapping mapKeyPath:@"name" toAttribute:@"name"];
tasteTagMapping.primaryKeyAttribute = @"tasteTagID";
[[RKObjectManager sharedManager].mappingProvider setMapping:tasteTagMapping forKeyPath:@"taste_tags"]; 
[[RKObjectManager sharedManager].mappingProvider addObjectMapping:tasteTagMapping];

.. some more mapping ..

I have the data coming back from the RoR server and it's getting mapped to objects as expected. The Core Data entity also seems mapped fine after RestKit gets the request back:

"<TasteTag: 0x6e87170> (entity: TasteTag; id: 0x6e85d60 <x-coredata://03E4A20A-21F2-4A2D-92B4-C4424893D559/TasteTag/p5> ; data: <fault>)"

The issue is when I try to access properties on the objects the fault can't seem to be fire. At first I was just calling the properties, which always came back as nil (even though that should fire the fault):

for (TasteTag *tag in self.vintage.tasteTags) {
    [tagNames addObject:tag.name]; //get error of trying to add nil to array   
}

After looking into manually triggering faults (http://www.mlsite.net/blog/?p=518) I tried calling [tag willAccessValueForKey:nil] which results in:

Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x6e7b060 <x-coredata://03E4A20A-21F2-4A2D-92B4-C4424893D559/TasteTag/p5>''

Looking up the entity in the .sqlite based on the key (TasteTag/p5) does show it mapped to the one I'd expect.

Other posts relating to RestKit recommend disabling the object cache (which I'm not using) since this is usually caused by an entity being deleted. But at this stage I'm just reading, not deleting, and I have no cache in place.

If I just call [TasteTag allObjects] I'm able to get all the objects back fine and they load without issue. It's just in the case when they are faulted it seems.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

无需解释 2025-01-02 07:37:43

我找到了一个适合我的解决方案(我不确定它对您的情况有多大适用性,但我将其添加为答案,因为它为我解决了这个(或非常相似的)问题):

几天前,我运行了 RKTwitterCoreData 示例,发现它工作得很好,而我的,此时代码非常简单,并且做了几乎相同的事情,但没有。我有很多很多未完成的错误。因此,我决定修改所有处理 RestKit 的代码,以反映 RKTwitterCoreData 示例的执行方式。

我将把它分成几个部分,以尝试帮助您遵循我当时的思路(因为我不认为我们的问题是相同的)。

我最初的实现假设

由于 RestKit 可以将对象返回到 Core Data,我假设这些托管对象可以互换使用。例如,我可以按照与从远程 Web 服务检索的对象完全相同的方式使用 Core Data 中的对象。我什至可以将它们合并在一起以获得所有数据。

我错了

我注意到RKTwitterCoreData的代码根本不是这样流动的。我的代码中有很大一部分与他们的相匹配,但最大的区别是他们没有将这些对象视为可互换的。事实上,他们从未使用过从远程数据存储中获取的对象。相反,他们只是让它“从裂缝中消失”。我只能假设这意味着它们被添加到 Core Data 的数据存储中,因为它适用于他们,现在也适用于我。

详细信息

我的应用程序在修改代码以利用此流程后可以正常运行。我只能推测,我们看到的无法实现的错误与使用我们从 Web 服务返回的核心数据支持的对象有关。相反,如果您只是忽略这些然后执行提取,您将得到所有内容(包括最近的请求),并且您不应该遇到任何无法完成的错误。

详细来说,如果您查看 RKTwitterViewController,您会注意到第 45-61 行处理对象的加载:

- (void)loadObjectsFromDataStore {
    [_statuses release];
    NSFetchRequest* request = [RKTStatus fetchRequest];
    NSSortDescriptor* descriptor = [NSSortDescriptor sortDescriptorWithKey:@"createdAt" ascending:NO];
    [request setSortDescriptors:[NSArray arrayWithObject:descriptor]];
    _statuses = [[RKTStatus objectsWithFetchRequest:request] retain];
}

- (void)loadData {
    // Load the object model via RestKit    
    RKObjectManager* objectManager = [RKObjectManager sharedManager];
    [objectManager loadObjectsAtResourcePath:@"/status/user_timeline/RestKit" delegate:self block:^(RKObjectLoader* loader) {
        // Twitter returns statuses as a naked array in JSON, so we instruct the loader
        // to user the appropriate object mapping
        loader.objectMapping = [objectManager.mappingProvider objectMappingForClass:[RKTStatus class]];
    }];
}

一切看起来都很正常(至少与我最初加载的方式相比)。但看一下 objectLoader:didLoadObjects: 委托方法:

- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
    [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:@"LastUpdatedAt"];
    [[NSUserDefaults standardUserDefaults] synchronize];
    NSLog(@"Loaded statuses: %@", objects);
    [self loadObjectsFromDataStore];
    [_tableView reloadData];
}

该示例甚至没有触及 objects 参数! (当然除了 NSLog...)

结论/tl;dr

不要使用在 objectLoader:didLoadObjects: 就好像它们完全由核心数据支持一样。相反,忽略它们并从核心数据中重新获取。所有对象,包括上次请求的对象都在那里。否则,你会遇到无法弥补的错误(至少我是这样)。

I found a solution that worked for me (I am unsure of how applicable it will be to your situation, but I'm adding it as an answer since it solved this (or very similar) issue for me):

A couple days back, I ran the RKTwitterCoreData example and noticed it worked perfectly while mine, with very simple code at this point and doing nearly the same thing, didn't. I got a lot of unfulfilled faults. So I decided to modify all of my code dealing with RestKit to reflect how the RKTwitterCoreData example does it.

I'll split this into chunks to try and help you follow my line of thinking at the time (since I don't think our problems are identical).

My Original Implementation Assumption

Since RestKit can back objects to Core Data, I assumed that those managed objects could be used interchangeably. For example, I could use the objects from Core Data in the exact same way as the ones retrieved from a remote web service. I could even merge them together to get all the data.

I Was Wrong

I noticed that RKTwitterCoreData's code did not flow this way in the least. A decent chunk of my code matched up with theirs, but the largest difference was that they didn't treat these objects as interchangeable. In fact, they never used the objects they got from remote data stores. Instead, they just let that "fall through the cracks". I can only assume that means they're added to Core Data's data store since it works for them and, now, for me.

Details

My app worked after modifying my code to utilize this flow. I can only then surmise that the unfulfillable faults we are seeing are related to using the Core Data backed objects that we get back from the web service. If instead you just ignore those and then do a fetch, you will get everything back (including the most recent request) and you shouldn't get any unfulfillable faults.

To elaborate, if you look at RKTwitterViewController you will notice that lines 45-61 handle loading of objects:

- (void)loadObjectsFromDataStore {
    [_statuses release];
    NSFetchRequest* request = [RKTStatus fetchRequest];
    NSSortDescriptor* descriptor = [NSSortDescriptor sortDescriptorWithKey:@"createdAt" ascending:NO];
    [request setSortDescriptors:[NSArray arrayWithObject:descriptor]];
    _statuses = [[RKTStatus objectsWithFetchRequest:request] retain];
}

- (void)loadData {
    // Load the object model via RestKit    
    RKObjectManager* objectManager = [RKObjectManager sharedManager];
    [objectManager loadObjectsAtResourcePath:@"/status/user_timeline/RestKit" delegate:self block:^(RKObjectLoader* loader) {
        // Twitter returns statuses as a naked array in JSON, so we instruct the loader
        // to user the appropriate object mapping
        loader.objectMapping = [objectManager.mappingProvider objectMappingForClass:[RKTStatus class]];
    }];
}

Everything looks normal (at least compared to how I was doing this loading initially). But take a look at the objectLoader:didLoadObjects: delegate method:

- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
    [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:@"LastUpdatedAt"];
    [[NSUserDefaults standardUserDefaults] synchronize];
    NSLog(@"Loaded statuses: %@", objects);
    [self loadObjectsFromDataStore];
    [_tableView reloadData];
}

The sample doesn't even touch the objects parameter! (Aside from the NSLog of course...)

Conclusion/tl;dr

Don't use the managed objects you get back in objectLoader:didLoadObjects: as if they are fully backed by Core Data. Instead, ignore them and re-fetch from Core Data. All objects, including the ones from the last request are there. Otherwise, you will get unfulfillable faults (at least I did).

宫墨修音 2025-01-02 07:37:43

根据 Ryan 的建议记录我的修复(阅读:hack)。

错误似乎在于 RestKit 假设您将使用从其 objectLoader:didLoadObjects: 方法返回的对象。他们似乎假设它将全部由核心数据支持(并遵循与 Ryan 谈论的类似的流程 - 让它同步到核心数据,然后重新查询),或者您将使用所有非核心数据支持的对象,并且只是保留这些结果。

就我而言,我有一个混合 - 非核心数据支持的对象的根数组,每个对象都包含一组核心数据支持的实体。顶级对象是我不介意查询服务器的对象,并且没有理由在它们显示的视图之外本地保留。似乎一旦 objectLoader:didLoadObjects: 完成托管支持 objects 参数中的核心数据实体的对象上下文被丢弃(假设您将重新查询它们),导致将来对实体的任何调用都会被视为错误,即使你不能触发错误并加载数据(导致NSObjectInaccessibleException)。

我用一个丑陋的黑客解决了这个问题 - 在 objectLoader:didLoadObjects: 中,我访问了核心数据实体的托管对象上下文之一,并将其​​复制到视图中的属性(self.context = [标记管理对象上下文];)。这可以防止在 objectLoader:didLoadObjects: 完成后释放上下文,从而允许我稍后在视图中访问实体而不会出现问题。

另一种解决方案是使用新上下文手动重新查询每个实体并将其复制回存储的返回对象。人们可以在显示它们时执行此操作,或者可能使用新的上下文在 objectLoader:didLoadObjects: 中进行一些后处理。实体 ID 仍然存在于故障对象上,因此即使在原始 RestKit 上下文消失后,也可以使用它来重新查询而不会出现问题。但必须像这样重新查询对象图中的每个实体似乎很愚蠢。

Documenting my fix (read:hack) per Ryan's suggestion.

The error seems to be in how RestKit assumed you'll be using the objects returned from their objectLoader:didLoadObjects: method. They seem to assume it will all be Core Data backed (and follow the flow similar to what Ryan talked about - let it sync to Core Data, then re-query) or that you'll be using all non Core Data backed objects and just keep those results around.

In my case I had a mix - a root array of non Core Data backed objects which each then contained an array of Core Data backed entities. The top-level objects are ones I don't mind querying the server for and have no reason to persist locally beyond the view they're shown in. It seems once objectLoader:didLoadObjects: is complete the managed object context backing the Core Data entities within the objects param is disposed of (under the assumption you'll be re-querying for them), causing any future calls to the entities to result in being treated as faults, even though you can't trigger the fault and load the data (results in NSObjectInaccessibleException).

I got around it with an ugly hack - within objectLoader:didLoadObjects: I access one of the Core Data entity's managed object context and copy it to a property within the view (self.context = [tag managedObjectContext];). This prevents the context it being released after objectLoader:didLoadObjects: is complete, allowing me to access the entities without issue later in the view.

Another solution would be to manually re-query for each entity using a new context and copy that back to the stored return objects. One could do this when one goes to display them, or possibly some post-processing in objectLoader:didLoadObjects:, using a new context. The entity ID is still around on the faulted object so one could use that to re-query without issue even after the original RestKit context disappears. But it seems silly to have to re-query for every entity in the object graph like that.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文