What is the relationship between the post object, in the RestKit postObject method, and the RKMappingResult it returns?

StackOverflow https://stackoverflow.com/questions/22343101

Question

I have been looking through the documentation on RestKit, but I haven't been able to work out some of the specifics on RKMappingResult.

I have code that creates an NSManagedObject, called newUser, and inserts in the mainQueueManagedObjectContext of the RestKit RKManagedObjectStore. I then use my object manager's postObject:path:parameters:success:failure: method to post the user to the server. This appears to work fine.

I need to update some fields on the newUser managed object when the post request completes, that will not be mapped from the result of the post request. The mappingResult parameter passed into the block seems to have the managed object that is properly mapped with all of the fields set from the response of the server.

Obviously, the object in the mappingResult is not the same object as the one that I posted since they are not in the same thread. If I save newUser before I post, will their objectID's be the same when the request finishes and it returns the mapping result?

I am imagining that both the newUser and the object from the mapping result refer to the same object that is stored in CoreData, is the correct? The reason I ask is that, this appears to not be the case. If I make one post request and save both the newUser object and the object returned in the mappingResult, I end up with two different objects in Core Data, one for the newUser and one for the mapping result. I confirmed this by changing a field on both the mappingResult object and the newUser object and seeing that they were both changed independently. Additionally, fetches two user objects the next time that I fetch. How do I prevent this?

If, however that is correct, what happens if I crash will the request has not yet completed? Won't I have a stub object just hanging around in CoreData?

If the request fails, how do I delete the stub object that I created? Could I do something like the following, in the failure block?

        dispatch_async(dispatch_get_main_queue(), ^{
            [newUser.managedObjectContext deleteObject:newRegisteredUser];
        });

Where newUser, is the managed object that I created on the mainQueue.

Lastly, if I get back a managed object in a background thread via the mapping result I can't set the returnedUser to be the user that the UI is using to get information from since that is on the main thread, so how do I notify the UI to change it's user? Should I post a notification that tells the UI to refetch the active user from Core Data?

UPDATE REGARDING DUPLICATE OBJECT

I have done some further testing and I am definitely getting a duplicate object when the post returns.

I first create a newUser and set properties on it:

HNGRRegisteredUser *newUser = [NSEntityDescription insertNewObjectForEntityForName:@"RegisteredUser" inManagedObjectContext:context];
newUser.firstName = @"John"
newUser.lastName = @"Smith"
newUser.gender = @"Male"
newUser.email = @"js@email.com"
newUser.hungrosityModel = //fill in all of the attributes for a hungrosityModel, except uniqueID

At this point, newUser.uniqueID == nil. I then save newUser (which I am assuming I have to do so RestKit can use the permanent objectID), to make a stub object.

NSError *saveError;
[newRegisteredUser.managedObjectContext saveToPersistentStore:&saveError];
if (saveError) NSLog(@"Save Error: %@", saveError);
NSLog(@"newUser objectID: %@", [newRegisteredUser.objectID URIRepresentation]);

I then post the newUser object.

[[RKObjectManager sharedManager] postObject:newRegisteredUser path:@"users/" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
    NSError *saveError;
    HNGRRegisteredUser *returnedUser = [mappingResult firstObject];
    NSLog(@"mappingResult objectID: %@", [returnedUser.objectID URIRepresentation]);

    [returnedUser.managedObjectContext saveToPersistentStore:&saveError];
    if (saveError) NSLog(@"Error saving user upon registration: %@", saveError);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
    [newUser.managedObjectContext deleteObject:newUser];
}];

The print outs for the objectIDs are below.

newUser objectID: x-coredata://B74AB613-1060-4D98-ABFC-3B4D89AB12C3/RegisteredUser/p2
mappingResult objectID: x-coredata://B74AB613-1060-4D98-ABFC-3B4D89AB12C3/RegisteredUser/p3

The JSON that was sent to the server was:

{
    "gender":"Male",
    "lastName":"Smith",
    "firstName":"John",
    "email":"js@email.com",
    "hungrosityModel": {
        "totalNumberOfUpdates":0,
        "peakHungrosity":0.8,
        "hungrosityFractionAtLastUpdate":0,
        "rateOfHungrosification":5e-05,
        "troughHungrosity":0.1,
        "peakUpdates":0
    }
}

The JSON that was returned from the server was:

{"results": [
    {"receivedFriendRequests": [],
     "firstName": "John",
     "middleName": null,
     "hungrosityModel": {"uniqueID": 13},
     "email": "js@email.com",
     "gender": "Male",
     "lastName": "Smith",
     "sentFriendRequests": [],
     "uniqueID": 14,
     "updatedAt": "2014-03-12T17:45:01.973Z",
     "friends": [],
     "profileImageUpdatedAt": null,
     "createdAt": "2014-03-12T17:45:01.809Z"}
 ]}

The response and the request descriptors for the object are:

[[RKObjectManager sharedManager].router.routeSet addRoute:[RKRoute routeWithClass:[HNGRRegisteredUser class] pathPattern:@"users/" method:RKRequestMethodPOST]];

RKResponseDescriptor *registrationResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[HNGRRKMappingProvider registeredUserMapping] method:RKRequestMethodPOST pathPattern:@"users/" keyPath:@"results" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[[RKObjectManager sharedManager] addResponseDescriptor: registrationResponseDescriptor];

RKRequestDescriptor *registrationRequestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:[[HNGRRKMappingProvider registeredUserMapping] inverseMapping] objectClass:[HNGRRegisteredUser class] rootKeyPath:nil method:RKRequestMethodPOST];
[[RKObjectManager sharedManager] addRequestDescriptor:registrationRequestDescriptor];

The mapping for the user is:

RKEntityMapping *_mapping = nil;
_mapping = [RKEntityMapping mappingForEntityForName:@"RegisteredUser" inManagedObjectStore:[RKManagedObjectStore defaultStore]];
[_mapping addAttributeMappingsFromArray:@[@"uniqueID",
                                           @"createdAt",
                                           @"updatedAt",
                                           @"firstName",
                                           @"middleName",
                                           @"lastName",
                                           @"email",
                                           @"gender",
                                           @"profileImageUpdatedAt"]];
[_mapping addRelationshipMappingWithSourceKeyPath:@"hungrosityModel" mapping:[HNGRRKMappingProvider basicModelMapping]];
_mapping.identificationAttributes = @[@"uniqueID"];

Here is the relevant object mapping trace:

D restkit.object_mapping:RKMapperOperation.m:377 Executing mapping operation for representation: {
        results =     (
                    {
                createdAt = "2014-03-12T18:22:35.688Z";
                dateOfBirth = "2014-03-12T18:24:02Z";
                firstName = John;
                friends =             (
                );
                gender = Male;
                hungrosityModel =             {
                    uniqueID = 14;
                };
                lastName = Smith;
                middleName = "<null>";
                profileImageUpdatedAt = "<null>";
                receivedFriendRequests =             (
                );
                sentFriendRequests =             (
                );
                uniqueID = 14;
                updatedAt = "2014-03-12T18:22:35.856Z";
            }
        );
    }
     and targetObject: <HNGRRegisteredUser: 0xaa8c180> (entity: RegisteredUser; id: 0xa894550 <x-coredata://44B8DF7A-BDC9-45F2-A137-BFAACF4AAF88/RegisteredUser/p2> ; data: {
        createdAt = nil;
        dateOfBirth = "2014-03-12 18:24:02 +0000";
        email = "js@email.com";
        firstName = John;
        friendRequests = "<relationship fault: 0xa899dd0 'friendRequests'>";
        friends = "<relationship fault: 0xa891ea0 'friends'>";
        gender = Male;
        hungrosityModel = "0xa861980 <x-coredata://44B8DF7A-BDC9-45F2-A137-BFAACF4AAF88/BasicModel/p2>";
        hungrosityUpdateEvents = "<relationship fault: 0xa829d90 'hungrosityUpdateEvents'>";
        isLocalUser = 0;
        lastName = Smith;
        middleName = nil;
        myInvitations = nil;
        profileImageData = nil;
        profileImageThumbnailData = nil;
        profileImageUpdatedAt = nil;
        receivedInvitations = nil;
        registeredUserSettings = nil;
        savedHungrosityComments = "<relationship fault: 0xa8bc450 'savedHungrosityComments'>";
        uniqueID = nil;
        updatedAt = nil;
        user = nil;
        userSettings = nil;
    })
  T restkit.object_mapping:RKMapperOperation.m:320 Examining keyPath 'results' for mappable content...
  D restkit.object_mapping:RKMapperOperation.m:297 Found mappable collection at keyPath 'results': (
            {
            createdAt = "2014-03-12T18:22:35.688Z";
            dateOfBirth = "2014-03-12T18:24:02Z";
            firstName = John;
            friends =         (
            );
            gender = Male;
            hungrosityModel =         {
                uniqueID = 14;
            };
            lastName = Smith;
            middleName = "<null>";
            profileImageUpdatedAt = "<null>";
            receivedFriendRequests =         (
            );
            sentFriendRequests =         (
            );
            uniqueID = 14;
            updatedAt = "2014-03-12T18:22:35.856Z";
        }
    )

 D restkit.object_mapping:RKMappingOperation.m:952 Starting mapping operation...
 T restkit.object_mapping:RKMappingOperation.m:953 Performing mapping operation: <RKMappingOperation 0xa8e4440> for 'HNGRRegisteredUser' object. Mapping values from object {
    createdAt = "2014-03-12T18:22:35.688Z";
    dateOfBirth = "2014-03-12T18:24:02Z";
    firstName = John;
    friends =     (
    );
    gender = Male;
    hungrosityModel =     {
        uniqueID = 14;
    };
    lastName = Smith;
    middleName = "<null>";
    profileImageUpdatedAt = "<null>";
    receivedFriendRequests =     (
    );
    sentFriendRequests =     (
    );
    uniqueID = 14;
    updatedAt = "2014-03-12T18:22:35.856Z";
} to object <HNGRRegisteredUser: 0xa8833b0> (entity: RegisteredUser; id: 0xa8777e0 <x-coredata:///RegisteredUser/t1FD3D8DE-75AD-4194-B157-EB6931B74BDB6> ; data: {
    createdAt = nil;
    dateOfBirth = nil;
    email = nil;
    firstName = nil;
    friendRequests =     (
    );
    friends =     (
    );
    gender = nil;
    hungrosityModel = nil;
    hungrosityUpdateEvents =     (
    );
    isLocalUser = 0;
    lastName = nil;
    middleName = nil;
    myInvitations = nil;
    profileImageData = nil;
    profileImageThumbnailData = nil;
    profileImageUpdatedAt = nil;
    receivedInvitations = nil;
    registeredUserSettings = nil;
    savedHungrosityComments =     (
    );
    uniqueID = 14;
    updatedAt = nil;
    user = nil;
    userSettings = nil;
}) with object mapping (null)

Let me know if there is any more information that would be helpful to know.

Was it helpful?

Solution

Yes, the object id will always match (even if the managed object instance is different it still represents the same underlying entity instance). Often they will be the same managed object instance as you start on the main thread and RestKit calls you back on the main thread too.

The new instance you describe doesn't happen. The original object you post should be updated directly. In your case that isn't happening because the server is returning an array of objects. This is causing RestKit to ignore the supplied destination object and create new ones. In this case that's one new object because the array contains one item. To fix this you will need to modify the JSON coming from the server as you can't index into the array as part of the mapping...

To delete, you don't need to switch thread (though you can if you want for future proofing), and that is the correct approach.

You can post a notification. I usually use a fetched results controller as it manages observing changes for you.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top