Search code examples
ioscore-datarestkit

Restkit local data mapping operation doesn't seem to save to disk


I'm currently using RestKit version 0.26.0.

I am performing a local mapping operation (it takes a local .json file, and parses it into objective c core data objects). I am doing it like so:

/*
 From:
 http://stackoverflow.com/questions/26556883/restkit-sync-data-base-with-local-json-file
 */
NSString* const MIMEType = @"application/json";

NSString* resourceName = [route.pathPattern stringByReplacingOccurrencesOfString:@"/" withString:@"--"];
NSString* data_filePath = [[NSBundle mainBundle] pathForResource:resourceName ofType:@"json"];
NSData *data = [NSData dataWithContentsOfFile:data_filePath];

NSError* error = nil;

id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:MIMEType error:&error];
if (parsedData == nil && error) {
    // Parser error...
}

RKManagedObjectStore *managedObjectStore = self.objectManager.managedObjectStore;
RKManagedObjectMappingOperationDataSource *mappingDataSource = [[RKManagedObjectMappingOperationDataSource alloc]
                                                                initWithManagedObjectContext:managedObjectStore.mainQueueManagedObjectContext
                                                                cache:managedObjectStore.managedObjectCache];

static NSMutableArray<RKMapperOperation*>* mapperOperations;
if (mapperOperations == nil)
{
    mapperOperations = [NSMutableArray array];
}

RKMapperOperation* mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:parsedData
                                                                    mappingsDictionary:objectMappings];

[mapperOperation setMappingOperationDataSource:mappingDataSource];

[mapperOperations addObject:mapperOperation];

dispatch_async(dispatch_get_main_queue(), ^{
    NSError *mappingError = nil;
    BOOL isMapped = [mapperOperation execute:&mappingError];

    if (isMapped && !mappingError)
    {
        success(mapperOperation,mapperOperation.mappingResult);
    }
    else
    {
        failure(mapperOperation,mappingError,false);
    }

    NSUInteger mapperOperation_index = [mapperOperations indexOfObject:mapperOperation];
    BOOL remove_mapperOperation = (mapperOperation_index < mapperOperations.count);
    NSCAssert(remove_mapperOperation, @"unhandled");
    if (remove_mapperOperation)
    {
        [mapperOperations removeObjectAtIndex:mapperOperation_index];
    }
});

return mapperOperation;

By the time a request hits its success block, everything seems to be in order - I get all my objects parsed how I want, passed through the mapping result. Not only that, but I am able to fetch those objects using a NSFetchRequest and my RKObjectManager's managedObjectStore.mainQueueManagedObjectContext. For example, the following returns my auth token:

-(QDAuthenticationToken*)fetchAuthenticationTokenFromStore
{
NSManagedObjectContext* context = [QDNetworkManager sharedInstance].objectManagerMainQueueManagedObjectContext;

__block QDAuthenticationToken* authenticationToken = nil;

NSFetchRequest *fetchRequest = [NSFetchRequest new];
[fetchRequest setFetchLimit:1];
[fetchRequest setEntity:[QDAuthenticationToken entityInManagedObjectContext:context]];

[context performBlockAndWait:^{

    NSError* fetchError = nil;
    NSArray* fetchedObjects = [context executeFetchRequest:fetchRequest error:&fetchError];

    kRUConditionalReturn(fetchError != nil, YES);
    kRUConditionalReturn(fetchedObjects.count != 1, NO);

    authenticationToken = kRUClassOrNil(fetchedObjects.firstObject, QDAuthenticationToken);

}];

return authenticationToken;
}

This method will reliably return me the single instance of QDAuthenticationToken I have in core data, after I've mapped one into core data in an app's lifetime. Unfortunately, once I kill the app, and attempt the above fetch, it then returns nil.

Although there are definitely other possible explanations, this leads me to believe that these RKMapperOperation instances aren't successfully writing these objects to disk. Could anyone provide any help on this?

I can also provide my code for setting up the RKObjectManager, but I use restkit in a few other projects with the exact same setup code, so I very much doubt the issue is my setup with the object manager.

**** Update with correct answer, thanks to Wain's comment ****

/*
 From:
 http://stackoverflow.com/questions/26556883/restkit-sync-data-base-with-local-json-file
 */
NSString* const MIMEType = @"application/json";

NSString* resourceName = [route.pathPattern stringByReplacingOccurrencesOfString:@"/" withString:@"--"];
NSString* data_filePath = [[NSBundle mainBundle] pathForResource:resourceName ofType:@"json"];
NSData *data = [NSData dataWithContentsOfFile:data_filePath];

NSError* error = nil;

id parsedData = [RKMIMETypeSerialization objectFromData:data MIMEType:MIMEType error:&error];
if (parsedData == nil && error) {
    // Parser error...
}

RKManagedObjectStore *managedObjectStore = self.objectManager.managedObjectStore;
NSManagedObjectContext* context = managedObjectStore.mainQueueManagedObjectContext;
RKManagedObjectMappingOperationDataSource *mappingDataSource = [[RKManagedObjectMappingOperationDataSource alloc]
                                                                initWithManagedObjectContext:context
                                                                cache:managedObjectStore.managedObjectCache];

static NSMutableArray<RKMapperOperation*>* mapperOperations;
if (mapperOperations == nil)
{
    mapperOperations = [NSMutableArray array];
}

RKMapperOperation* mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:parsedData
                                                                    mappingsDictionary:objectMappings];

[mapperOperation setMappingOperationDataSource:mappingDataSource];

@synchronized(mapperOperations) {
    [mapperOperations addObject:mapperOperation];
}

dispatch_async(dispatch_get_main_queue(), ^{

    __block BOOL operationSuccess = false;
    __block NSError* operationError = nil;
    [context performBlockAndWait:^{

        NSError *mappingError = nil;
        BOOL isMapped = [mapperOperation execute:&mappingError];

        if ((isMapped == false) ||
            (mappingError != nil))
        {
            operationError = mappingError;
            return;
        }

        NSError* saveError = nil;
        BOOL saveSuccess = [context saveToPersistentStore:&saveError];

        if ((saveSuccess == false) ||
            (saveError != nil))
        {
            operationError = saveError;
            return;
        }

        operationSuccess = YES;

    }];

    if ((operationSuccess == TRUE) &&
        (operationError == nil))
    {
        success(mapperOperation,mapperOperation.mappingResult);
    }
    else
    {
        failure(mapperOperation,operationError,false);
    }


    @synchronized(mapperOperations) {
        NSUInteger mapperOperation_index = [mapperOperations indexOfObject:mapperOperation];
        BOOL remove_mapperOperation = (mapperOperation_index < mapperOperations.count);
        NSCAssert(remove_mapperOperation, @"unhandled");
        if (remove_mapperOperation)
        {
            [mapperOperations removeObjectAtIndex:mapperOperation_index];
        }
    }
});

return mapperOperation;

Solution

  • RKMapperOperation doesn't know anything about the context or saving it. When you execute the operation is performs a mapping and that is all.

    RKManagedObjectMappingOperationDataSource creates objects and inserts them into the context when requested. These objects are then updated by the RKMapperOperation during the mapping. The data source isn't asked to save after that - it isn't the responsibility of the data source or mapper operation to save after they're done.

    So, you need to explicitly save the context (and, depending on what context you're using, any parents) after the mapper operation completes.

    Your fetch request works fine because it doesn't require the context to be saved before it returns objects.