Objective c Restkit加载的嵌套核心数据实体导致NSObjectInAccessibleeException

Objective c Restkit加载的嵌套核心数据实体导致NSObjectInAccessibleeException,objective-c,ios,core-data,nsmanagedobject,restkit,Objective C,Ios,Core Data,Nsmanagedobject,Restkit,我正在使用RestKit从RoR服务中获取对象,并使用CoreData持久化一些对象(更多的静态类型查找表对象)。TasteTag是以下持久化对象之一: #ifdef RESTKIT_GENERATE_SEED_DB NSString *seedDatabaseName = nil; NSString *databaseName = RKDefaultSeedDatabaseFileName; #else NSString *seedDatabaseName = RKDe

我正在使用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>)"
查看手动触发故障()后,我尝试调用
[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>''
由于未捕获的异常“NSObjectInaccessibleException”而终止应用程序,原因:“CoreData无法实现“0x6e7b060”的故障”
根据键(TasteTag/p5)在.sqlite中查找实体时,会显示它映射到我期望的实体

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


如果我只调用
[TasteTag allObjects]
我就能够很好地恢复所有对象,并且它们加载时不会出现问题。这只是在他们出现错误的情况下出现的。

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

几天前,我运行了
RKTwitterCoreData
示例,发现它工作得很好,而我的示例在这一点上代码非常简单,做的事情几乎相同,却没有。我有很多未完成的错误。因此,我决定修改所有处理
RestKit
的代码,以反映
RKTwitterCoreData
示例是如何实现的

我将把这篇文章分成几部分,试图帮助您遵循我当时的思路(因为我认为我们的问题并不相同)

我最初的实施假设

由于RestKit可以将对象返回到核心数据,所以我假设这些托管对象可以互换使用。例如,我可以使用来自核心数据的对象,使用方式与从远程web服务检索的对象完全相同。我甚至可以把它们合并在一起得到所有的数据

我错了

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

详细信息

我的应用程序在修改代码以利用此流后工作。我只能猜测,我们所看到的无法实现的错误与使用我们从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:
delegate方法:

- (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];
}
该示例甚至没有触及
对象
参数!(当然除了
NSLog
之外…)

结论/tl;dr

不要使用在
objectLoader:didLoadObjects:
中返回的托管对象,就好像它们完全由核心数据支持一样。相反,忽略它们并从核心数据重新获取。所有对象,包括上次请求中的对象都存在。否则,您将得到无法实现的故障(至少我有过)。

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

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

在我的例子中,我有一个混合——一个由非核心数据支持的对象组成的根数组,每个对象都包含一个由核心数据支持的实体组成的数组。顶级对象是我不介意查询服务器的对象,并且没有理由在显示它们的视图之外本地持久化。似乎一旦
objectLoader:didLoadObjects:
完成,
objects
param中支持核心数据实体的托管对象上下文就被处理掉了(假设您将重新查询它们),导致以后对实体的任何调用都被视为错误,即使您无法触发故障并加载数据(结果是
NSObjectInaccessibleException

我在
objectLoader:didLoadObjects:
中使用了一个难看的技巧,我访问了核心数据实体的一个托管对象上下文,并将其复制到视图中的属性(
self.context=[tag managedObjectContext];
)。这可以防止在
objectLoader:didLoadObjects:
完成后释放上下文,从而允许我在视图中访问实体而不会出现问题

另一种解决方案是使用新上下文手动重新查询每个实体,并将其复制回存储的返回对象。可以在使用新上下文显示它们时执行此操作,也可以在
objectLoader:didLoadObjects:
中进行一些后期处理。实体ID仍在故障对象上,因此可以
- (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];
}