Загруженные Restkit вложенные объекты Core Data вызывают NSObjectInaccessibleException

Я использую 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, и они сопоставляются с объектами, как и ожидалось. Сущность Core Data также отображается нормально после того, как 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], я смогу вернуть все объекты обратно, и они загружаются без проблем. Это как раз в том случае, когда они виноваты кажется.


person Parrots    schedule 12.01.2012    source источник
comment
Надеюсь, вы найдете решение, у меня почти такая же проблема.   -  person Ryan Wersal    schedule 13.01.2012
comment
@ryan, если найдешь что-нибудь, дай мне знать, пока мне не везло   -  person Parrots    schedule 13.01.2012
comment
@ryan - похоже, это не зависит от RestKit. Дополнительный вопрос здесь: stackoverflow.com/questions/8856867/   -  person Parrots    schedule 14.01.2012
comment
Я нашел решение, которое сработало для меня. Я попытался быть явным в своем ответе ниже. Надеюсь, поможет!   -  person Ryan Wersal    schedule 16.01.2012


Ответы (2)


Я нашел решение, которое сработало для меня (я не уверен, насколько оно применимо к вашей ситуации, но я добавляю его в качестве ответа, поскольку оно решило эту (или очень похожую) проблему для меня):

Пару дней назад я запустил пример RKTwitterCoreData и заметил, что он работает отлично, в то время как мой, с очень простым кодом на тот момент и делающий почти то же самое, не работает. У меня много невыполненных ошибок. Поэтому я решил изменить весь свой код, связанный с RestKit, чтобы он отражал то, как это делает пример RKTwitterCoreData.

Я разделю это на куски, чтобы попытаться помочь вам следовать моему мышлению в то время (поскольку я не думаю, что наши проблемы идентичны).

Мое первоначальное предположение о реализации

Поскольку RestKit может возвращать объекты в Core Data, я предположил, что эти управляемые объекты можно использовать взаимозаменяемо. Например, я мог бы использовать объекты из Core Data точно так же, как объекты, полученные из удаленного веб-сервиса. Я мог бы даже объединить их вместе, чтобы получить все данные.

Я ошибался

Я заметил, что код RKTwitterCoreData не совсем работает таким образом. Приличный кусок моего кода совпадал с их, но самая большая разница заключалась в том, что они не рассматривали эти объекты как взаимозаменяемые. На самом деле они никогда не использовали объекты, полученные из удаленных хранилищ данных. Вместо этого они просто позволили этому «провалиться сквозь трещины». Я могу только предположить, что это означает, что они добавлены в хранилище данных Core Data, так как это работает для них, а теперь и для меня.

Подробнее

Мое приложение заработало после изменения моего кода для использования этого потока. Я могу только тогда предположить, что неисполнимые ошибки, которые мы наблюдаем, связаны с использованием объектов, поддерживаемых Core Data, которые мы получаем обратно из веб-службы. Если вместо этого вы просто проигнорируете их, а затем выполните выборку, вы получите все обратно (включая самый последний запрос) и у вас не должно быть никаких невыполнимых ошибок.

Чтобы уточнить, если вы посмотрите на 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:, как если бы они полностью поддерживались Core Data. Вместо этого проигнорируйте их и повторите выборку из Core Data. Все объекты, в том числе из последнего запроса, есть. В противном случае вы получите невыполнимые ошибки (по крайней мере, у меня).

person Ryan Wersal    schedule 16.01.2012
comment
Я пришел к тому же выводу во время отладки. Кажется, они предполагают, что вы позволите ему синхронизироваться с компакт-диском, а затем повторно запросите. Проблема, с которой я столкнулся, заключается в том, что я не планировал иметь все свои объекты на компакт-диске (не нужно хранить их на потом - только этот рендеринг одного вида). Это заставляет меня использовать возвращенные объекты. В итоге я исправил (читай: уродливым образом взломал) это, сохранив управляемый контекст NSManagedObject в viewController. Это позволяет мне получать доступ к объектам в cellForIndexPath. - person Parrots; 16.01.2012
comment
Возможно, стоит потратить время, чтобы добавить это в качестве ответа для любых других неудачливых путешественников по этому аспекту RestKit. Я впечатлен библиотекой, но мне кажется странным, что это предостережение не документировано лучше. - person Ryan Wersal; 16.01.2012
comment
Согласовать документацию. Я мог бы действительно использовать некоторые современные документы вместо старых документов. Спасибо за понимание здесь. - person Neru-J; 16.07.2012

Документирование моего исправления (читай: взлом) по предложению Райана.

Ошибка, похоже, заключается в том, как RestKit предположил, что вы будете использовать объекты, возвращаемые их методом objectLoader:didLoadObjects:. Похоже, они предполагают, что все это будет поддерживаться Core Data (и следовать процессу, аналогичному тому, о чем говорил Райан — позволить ему синхронизироваться с Core Data, а затем повторно запрашивать) или что вы будете использовать все объекты, не поддерживаемые Core Data, и просто сохранить эти результаты вокруг.

В моем случае у меня был микс — корневой массив объектов, не поддерживаемых Core Data, каждый из которых затем содержал массив объектов, поддерживаемых Core Data. Объекты верхнего уровня — это те, для которых я не возражаю запрашивать сервер, и у меня нет причин сохраняться локально за пределами представления, в котором они показаны. Кажется, что после завершения objectLoader:didLoadObjects: контекст управляемого объекта поддерживает объекты Core Data в objects param удаляется (при условии, что вы будете повторно запрашивать их), в результате чего любые будущие вызовы сущностей будут рассматриваться как ошибки, даже если вы не можете вызвать ошибку и загрузить данные (приводит к NSObjectInaccessibleException).

Я обошел это с помощью уродливого хака - в objectLoader:didLoadObjects: я получаю доступ к одному из контекстов управляемых объектов объекта Core Data и копирую его в свойство в представлении (self.context = [tag managedObjectContext];). Это предотвращает освобождение контекста после завершения objectLoader:didLoadObjects:, позволяя мне без проблем обращаться к объектам позже в представлении.

Другим решением может быть повторный запрос вручную для каждой сущности с использованием нового контекста и копирование его обратно в сохраненные возвращаемые объекты. Можно сделать это, когда кто-то собирается их отобразить, или, возможно, какую-то постобработку в objectLoader:didLoadObjects:, используя новый контекст. Идентификатор сущности по-прежнему присутствует в объекте с ошибкой, поэтому его можно использовать для повторного запроса без проблем даже после исчезновения исходного контекста RestKit. Но кажется глупым повторно запрашивать каждую сущность в графе объектов таким образом.

person Parrots    schedule 16.01.2012
comment
Вы можете отключить автоматическую синхронизацию и сохранять объекты в модели компакт-диска вручную, когда захотите. Но мне нужна документация по этому поводу! Спасибо, в любом случае :) - person Neru-J; 16.07.2012