1

This error has shown up on SO many times, it seems. The most prominent answer to this issue seems to be RestKit 0.20 — CoreData: error: Failed to call designated initializer on NSManagedObject class

However, the solution posted doesn't work for me. But let's start from the beginning:

I want to upload a file to a server. I want to parse the response to make sure it was successfully uploaded. If not, I will try again later. My model has a boolean variable called NSNumber *sentToServer for this purpose (in CoreData, BOOL is saved as NSNumber).

Because I don't know whether sending is going to be successful, I always save an entity to my CoreData model first before sending it via RestKit.

When the POST request is successful, the server returns a JSON string like this:

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

Notice that, of course, the ID is not set on the CoreData model yet, so both id and created are new informations for my model.

Here's my model:

@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

And here's how I set it up:

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];

Firstly, this is a surprisingly lengthy setup code for something that should be so common (just init RestKit and MagicalRecord). But I digress...

The whole upload process works fine. The request mappings are good, everything is stored on the server, including the attachments. Notice that the request mappings do not contain the files, since they are sent via multipart post methods. Again, that part works fine. Here's the code for uploading files:

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];

Mapping the response to an object becomes difficult. Without a matching response mapping, the log outputs contain an error that it couldn't handle the response (surprise). With a matching response mapping, however, the app crashes. Here is the stack trace:

#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:] ()

The line where the debugger stops with a SIGABRT in [RKMappingOperation shouldSetValue:forKeyPath:usingMapping:] is:

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

Note that when I set a breakpoint as suggested in https://stackoverflow.com/a/13734348/487556, that breakpoint is never reached. This implies that there are no issues with the path of the response mapping.

I can imagine several things here going wrong:

  1. RestKit assumes that there is no CoreData entity yet and wants to create one. This would lead to duplicate entities (which would be a bummer!), but it still shouldn't crash, since all fields of the model are optional.
  2. RestKit tries to find a CoreData entity based on the ID (since this is the identifier variable), but of course the ID is not set yet.
  3. Something else...

Any suggestions?

Community
  • 1
  • 1
Thomas Walther
  • 526
  • 1
  • 6
  • 15
  • Reskit cannot match the objects by the primary key because they do not have one locally. You should add another property - a global unique identifier - to your model to use as identification attributes instead of using recordingId. Also, remove the sentToServer property and just get the objects with recordingId == 0 to sync when the network connection is available. – serrrgi Aug 15 '13 at 22:23
  • How are you actually posting? How are you using the `objectManager`? – Wain Aug 16 '13 at 09:36
  • serrrgi, you are right on the sentToServer property. I am actually using this right now as a workaround. If I make the server respond with only an HTTP header, Restkit has nothing to map and therefore it succeeds. Then, in the success block of the RequestOperation, I can set sentToServer to true manually. I agree that for the response mapping I want to do, I don't need the sentToServer attribute! I don't understand why Restkit cannot match the objects, though. After all, in the success block, I can manually access the object I sent, without the need of any ID, so RK should be able to do so,too – Thomas Walther Aug 19 '13 at 17:07
  • Wain, I updated the post to include the code for the posting. – Thomas Walther Aug 19 '13 at 17:08

2 Answers2

1

I know it has been a while since you asked and maybe you've found your way since, but I just ran into the same situation and found the solution.

The problem comes from the fact that you use the objectRequestOperationWithRequest:success:failure: method while working with NSManagedObjects. Therefore, RestKit tries to instantiate a regular NSObject during the mapping operation, which leads to a crash.

Try using managedObjectRequestOperationWithRequest:managedObjectContext:success:failure: instead, and things should be better.

ink
  • 317
  • 2
  • 14
0

The problem is with matching path in response description and request. To make it work slashes in them should be the same, either @"/audio.json" or @"audio.json" otherwise RestKit doesn't see that one matches the other.

Zapko
  • 2,461
  • 25
  • 30