RestKit 0.20 - CoreData: ошибка: не удалось вызвать назначенный инициализатор в классе NSManagedObject (снова)

Кажется, эта ошибка появлялась на ТАК много раз. Наиболее очевидным ответом на эту проблему является RestKit 0.20 - CoreData: ошибка: не удалось вызвать назначенный инициализатор в классе NSManagedObject

Однако опубликованное решение не работает для меня. Но начнем с самого начала:

Я хочу загрузить файл на сервер. Я хочу проанализировать ответ, чтобы убедиться, что он был успешно загружен. Если нет, я попробую позже. В моей модели для этой цели есть логическая переменная NSNumber *sentToServer (в CoreData BOOL сохраняется как NSNumber).

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

Когда запрос POST успешен, сервер возвращает строку JSON, подобную этой:

{"id":14,"created":true}

Обратите внимание, что, конечно, идентификатор еще не установлен в модели CoreData, поэтому и id, и created являются новой информацией для моей модели.

Вот моя модель:

@interface Recording : NSManagedObject

@property (nonatomic, retain) NSString * audioFilePath;
@property (nonatomic, retain) NSDate * createdAt;
@property (nonatomic, retain) NSString * deviceId;
@property (nonatomic, retain) NSString * deviceType;
@property (nonatomic, retain) NSNumber * recordingId;
@property (nonatomic, retain) NSNumber * sentToServer;

@end

И вот как я это настроил:

NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"HTRecording" ofType:@"momd"]];
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"AudioModel.sqlite"];
NSError *error = nil;

NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                         [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                         [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

if (![managedObjectStore addSQLitePersistentStoreAtPath:storePath
                                 fromSeedDatabaseAtPath: nil
                                      withConfiguration:nil
                                                options:options
                                                  error:&error]) {
    NSAssert(NO, @"Managed object store failed to create persistent store coordinator: %@", error);
}
[managedObjectStore createManagedObjectContexts];

// Configure MagicalRecord to use RestKit's Core Data stack
[NSPersistentStoreCoordinator MR_setDefaultStoreCoordinator:managedObjectStore.persistentStoreCoordinator];
[NSManagedObjectContext MR_setRootSavingContext:managedObjectStore.persistentStoreManagedObjectContext];
[NSManagedObjectContext MR_setDefaultContext:managedObjectStore.mainQueueManagedObjectContext];

NSString *databaseUrl = @"http://localhost:3000";
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:databaseUrl]];
objectManager.managedObjectStore = managedObjectStore;
objectManager.requestSerializationMIMEType = RKMIMETypeJSON;

// Setup mapping
RKEntityMapping* recordingRequestMapping = [RKEntityMapping mappingForEntityForName:@"Recording" inManagedObjectStore:managedObjectStore];
[recordingRequestMapping addAttributeMappingsFromDictionary:@{
 @"device_id": @"deviceId",
 @"device_type": @"deviceType"     }];

RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[recordingRequestMapping inverseMapping]
                                                                               objectClass:[Recording class]
                                                                               rootKeyPath:@"recording"];
[objectManager addRequestDescriptor:requestDescriptor];

RKEntityMapping* recordingResponseMapping = [RKEntityMapping mappingForEntityForName:@"Recording" inManagedObjectStore:managedObjectStore];
[recordingResponseMapping addAttributeMappingsFromDictionary:@{
 @"created": @"sentToServer",
 @"id": @"recordingId"}];
recordingResponseMapping.identificationAttributes = @[@"recordingId"];

RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:recordingResponseMapping
                                                                                   pathPattern:@"/audio.json"
                                                                                       keyPath:nil
                                                                                   statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];

[objectManager addResponseDescriptor:responseDescriptor];

Во-первых, это удивительно длинный установочный код для чего-то, что должно быть настолько распространенным (просто запустите RestKit и MagicalRecord). Но я отвлекся ...

Весь процесс загрузки работает нормально. Отображения запросов хорошие, все хранится на сервере, включая вложения. Обратите внимание, что сопоставления запросов не содержат файлов, поскольку они отправляются с помощью методов multipart post. Опять же, эта часть работает нормально. Вот код для загрузки файлов:

RKObjectManager *manager = [RKObjectManager sharedManager];
NSMutableURLRequest *request;

request = [manager multipartFormRequestWithObject:recording
                                           method:RKRequestMethodPOST
                                             path:@"audio.json" 
                                       parameters:nil
                        constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
        [formData appendPartWithFileData:[[NSFileManager defaultManager] contentsAtPath:recording.audioFilePath]
                                    name:@"recording[audio]"
                                fileName:[recording.audioFilePath lastPathComponent]
                                mimeType:@"audio/m4a"];
    }];

RKObjectRequestOperation *operation = [manager objectRequestOperationWithRequest:request
                                                                         success:^(RKObjectRequestOperation *op, RKMappingResult *mapping) {
                                                                             NSLog(@"successfully saved file on the server");
                                                                         }
                                                                         failure:^(RKObjectRequestOperation *op, NSError *error){
                                                                             NSLog(@"some weird error");
                                                                         }];


[manager enqueueObjectRequestOperation:operation];

Отображение реакции на объект становится затруднительным. Без соответствующего сопоставления ответов выходные данные журнала содержат ошибку, которая не может обработать ответ (сюрприз). При совпадающем сопоставлении ответов приложение аварийно завершает работу. Вот трассировка стека:

#11 0x2f6482d6 in -[NSObject(NSKeyValueCoding) valueForKeyPath:] ()
#12 0x00113b9a in -[RKMappingOperation shouldSetValue:forKeyPath:usingMapping:] at /Users/earthnail/development/RestkitFileUpload/Pods/RestKit/Code/ObjectMapping/RKMappingOperation.m:436
#13 0x00114e40 in -[RKMappingOperation applyAttributeMapping:withValue:] at /Users/earthnail/development/RestkitFileUpload/Pods/RestKit/Code/ObjectMapping/RKMappingOperation.m:536
#14 0x00115fd4 in -[RKMappingOperation applyAttributeMappings:] at /Users/earthnail/development/RestkitFileUpload/Pods/RestKit/Code/ObjectMapping/RKMappingOperation.m:577
#15 0x0011c988 in -[RKMappingOperation main] at /Users/earthnail/development/RestkitFileUpload/Pods/RestKit/Code/ObjectMapping/RKMappingOperation.m:948
#16 0x2f655d0a in -[__NSOperationInternal _start:] ()
#17 0x0010cd5a in -[RKMapperOperation mapRepresentation:toObject:atKeyPath:usingMapping:metadata:] at /Users/earthnail/development/RestkitFileUpload/Pods/RestKit/Code/ObjectMapping/RKMapperOperation.m:256
#18 0x0010b6aa in -[RKMapperOperation mapRepresentation:atKeyPath:usingMapping:] at /Users/earthnail/development/RestkitFileUpload/Pods/RestKit/Code/ObjectMapping/RKMapperOperation.m:176
#19 0x0010ddc8 in -[RKMapperOperation mapRepresentationOrRepresentations:atKeyPath:usingMapping:] at /Users/earthnail/development/RestkitFileUpload/Pods/RestKit/Code/ObjectMapping/RKMapperOperation.m:314
#20 0x0010e552 in -[RKMapperOperation mapSourceRepresentationWithMappingsDictionary:] at /Users/earthnail/development/RestkitFileUpload/Pods/RestKit/Code/ObjectMapping/RKMapperOperation.m:359
#21 0x0010ee72 in -[RKMapperOperation main] at /Users/earthnail/development/RestkitFileUpload/Pods/RestKit/Code/ObjectMapping/RKMapperOperation.m:398
#22 0x2f655d0a in -[__NSOperationInternal _start:] ()
#23 0x00100e70 in -[RKObjectResponseMapperOperation performMappingWithObject:error:] at /Users/earthnail/development/RestkitFileUpload/Pods/RestKit/Code/Network/RKResponseMapperOperation.m:345
#24 0x000ff4e6 in -[RKResponseMapperOperation main] at /Users/earthnail/development/RestkitFileUpload/Pods/RestKit/Code/Network/RKResponseMapperOperation.m:297
#25 0x2f655d0a in -[__NSOperationInternal _start:] ()

Строка, в которой отладчик останавливается с SIGABRT в [RKMappingOperation shouldSetValue:forKeyPath:usingMapping:]:

id currentValue = [self.destinationObject valueForKeyPath:keyPath];

Обратите внимание, что когда я устанавливаю точку останова, как предложено в https://stackoverflow.com/a/13734348/487556, эта точка останова никогда не достигается. Это означает, что нет проблем с путем отображения ответа.

Я могу представить несколько вещей, которые здесь идут не так:

  1. RestKit предполагает, что сущности CoreData еще нет, и хочет ее создать. Это привело бы к дублированию сущностей (что было бы облом!), Но все равно не должно произойти сбоя, поскольку все поля модели являются необязательными.
  2. RestKit пытается найти сущность CoreData на основе идентификатора (поскольку это переменная идентификатора), но, конечно, идентификатор еще не установлен.
  3. Что-то другое...

Какие-либо предложения?


person Thomas Walther    schedule 15.08.2013    source источник
comment
Reskit не может сопоставить объекты по первичному ключу, потому что у них его нет локально. Вы должны добавить в свою модель еще одно свойство - глобальный уникальный идентификатор, чтобы использовать его в качестве идентификационных атрибутов вместо использования recordId. Кроме того, удалите свойство sentToServer и просто получите объекты с recordId == 0 для синхронизации, когда доступно сетевое соединение.   -  person serrrgi    schedule 16.08.2013
comment
Как вы на самом деле публикуете? Как вы используете objectManager?   -  person Wain    schedule 16.08.2013
comment
serrrgi, вы правы в свойстве sentToServer. Я использую это прямо сейчас как обходной путь. Если я заставлю сервер отвечать только HTTP-заголовком, Restkit не будет ничего отображать, и, следовательно, он будет успешным. Затем в блоке успеха RequestOperation я могу вручную установить для sentToServer значение true. Я согласен с тем, что для отображения ответов, которое я хочу сделать, мне не нужен атрибут sentToServer! Однако я не понимаю, почему Restkit не может сопоставить объекты. В конце концов, в блоке успеха я могу вручную получить доступ к объекту, который я отправил, без необходимости какого-либо идентификатора, поэтому RK тоже должен иметь возможность сделать это.   -  person Thomas Walther    schedule 19.08.2013
comment
Wain, я обновил пост, добавив код для публикации.   -  person Thomas Walther    schedule 19.08.2013


Ответы (2)


Я знаю, что прошло некоторое время с тех пор, как вы спросили, и, возможно, с тех пор вы нашли свой путь, но я просто столкнулся с той же ситуацией и нашел решение.

Проблема возникает из-за того, что вы используете метод objectRequestOperationWithRequest:success:failure: при работе с NSManagedObjects. Поэтому RestKit пытается создать экземпляр обычного NSObject во время операции сопоставления, что приводит к сбою.

Попробуйте вместо этого использовать managedObjectRequestOperationWithRequest:managedObjectContext:success:failure:, и все должно быть лучше.

person ink    schedule 28.04.2014

Проблема заключается в совпадении пути в описании ответа и запросе. Чтобы он работал, слэши в них должны быть одинаковыми, либо @"/audio.json", либо @"audio.json", иначе RestKit не увидит, что одно соответствует другому.

person Zapko    schedule 02.11.2013