3
votes

I'm running into an issue where I'm getting duplicate writes to my SQLite database every time I perform a mapping operation in RestKit 0.20.2. I've looked online pretty extensively and can't seem to find a duplicate issue to what I'm experiencing—all of the similar problems I've seen center around using RestKit for everything, while I'm using RestKit just for the mapping and database operations, as I'm doing the network aspect in another library.

I have a unique identificationAttribute property set (the tweet ID) and am using a managedObjectCache initialized with RKInMemoryManagedObjectCache; however, every time I perform a mapping operation, I'm getting duplicates across the board on everything. The library I'm using for the network side (STTwitter) returns the JSON as an array, so I iterate through each object in the array. Is there some other operation I'm supposed to be performing? I was under the impression that RestKit compares the identificationAttribute property specified in mapped objects to what's already in the SQLite database before doing any insertions. I wasn't encountering this issue when I used it for everything in another project, using RKManagedObjectRequestOperation.

Here's how I set up the model:

-(void)setup
{
self.objectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:[self managedObjectModel]];

[self.objectStore createPersistentStoreCoordinator];

NSString *path = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"FoodTruckxxx.sqlite"];
NSLog(@"Setting up store at %@", path);
[self.objectStore addSQLitePersistentStoreAtPath:path
                          fromSeedDatabaseAtPath:nil
                               withConfiguration:nil
                                         options:self.optionsForSQLiteStore
                                           error:nil];

[self.objectStore createManagedObjectContexts];

self.objectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:self.objectStore.persistentStoreManagedObjectContext];
}

and here's how I perform the mapping operation:

-(void)performMapping
{
int i = 0;
while (i < self.localCopyOfAllStatuses.count)
{
    RKManagedObjectStore *store = [[FoodTruckDataModel sharedDataModel] objectStore];
    RKEntityMapping *mapping = [ObjectMappings FoodTruckArticleMapping];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"FoodTruck" inManagedObjectContext:store.persistentStoreManagedObjectContext];
    NSManagedObject *newManagedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:store.persistentStoreManagedObjectContext];
    RKManagedObjectMappingOperationDataSource *mappingDS = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:store.persistentStoreManagedObjectContext cache:store.managedObjectCache];

    //assign new array to contain to just one object at a time and iterate through it
    NSArray *justOneStatus = [self.localCopyOfAllStatuses objectAtIndex:i];
    RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:justOneStatus destinationObject:newManagedObject mapping:mapping];
    operation.dataSource = mappingDS;
    NSError *error = nil;
    [operation performMapping:&error];
    [store.persistentStoreManagedObjectContext save:&error];
    [store.persistentStoreManagedObjectContext saveToPersistentStore:&error];
    i++;
}
}

And here is the mapping:

+(RKEntityMapping *)FoodTruckArticleMapping
{
RKEntityMapping *jsonMapping = [RKEntityMapping mappingForEntityForName:@"FoodTruck" inManagedObjectStore:[[FoodTruckDataModel sharedDataModel] objectStore]];
jsonMapping.identificationAttributes = @[@"tweetID"];

[jsonMapping addAttributeMappingsFromDictionary:@{
 @"text": @"tweet", @"user.screen_name": @"foodTruckName", @"created_at": @"timeStamp", @"id": @"tweetID"}];

return jsonMapping;
}

And here is a complete operation from my log. Any help would be greatly appreciated!

2
You're explicitly creating managed object instances in your loop. Why don't you give RestKit the entire JSON response and let it do all of the mapping and object creation?Wain
Wain- I do give it the JSON response and the mapping in RKMappingOperation, right? The sourceObject is a JSON response and mapping is my RKEntityMapping. What would I put for destinationObject in that call, then, other than a NSManagedObject?Evan R

2 Answers

2
votes

Per the answer to this question on the RestKit Google Group board (which I verified worked), the solution was to not create a NSManagedObject and to assign nil to destinationObject in my RKMappingOperation declaration. Below is the correct code for this method, and the answer to my initial question:

-(void)performMapping
{
    int i = 0;
    while (i < self.localCopyOfAllStatuses.count)
    {
        RKManagedObjectStore *store = [[FoodTruckDataModel sharedDataModel] objectStore];
        RKEntityMapping *mapping = [ObjectMappings FoodTruckArticleMapping];
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"FoodTruck" inManagedObjectContext:store.persistentStoreManagedObjectContext];
        RKManagedObjectMappingOperationDataSource *mappingDS = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:store.persistentStoreManagedObjectContext cache:store.managedObjectCache];

        //assign new array to contain to just one object at a time and iterate through it
        NSArray *justOneStatus = [self.localCopyOfAllStatuses objectAtIndex:i];
        RKMappingOperation *operation = [[RKMappingOperation alloc] initWithSourceObject:justOneStatus destinationObject:nil mapping:mapping];
        operation.dataSource = mappingDS;
        NSError *error = nil;
        [operation performMapping:&error];
        [store.persistentStoreManagedObjectContext save:&error];
        [store.persistentStoreManagedObjectContext saveToPersistentStore:&error];
        i++;
    }
}

However, it was also suggested in the RestKit Google Group link that I implement RKMapperOperation instead of RKMappingOperation, as it then saves me the trouble of iterating through my array of JSON responses. This is what I eventually went with, which I present for the sake of efficiency and better use of RestKit:

-(void)performMapping
{
    RKManagedObjectStore *store = [[FoodTruckDataModel sharedDataModel] objectStore];
    RKManagedObjectMappingOperationDataSource *mappingDS = [[RKManagedObjectMappingOperationDataSource alloc] initWithManagedObjectContext:store.persistentStoreManagedObjectContext cache:store.managedObjectCache];
    RKMapperOperation *mapperOperation = [[RKMapperOperation alloc] initWithRepresentation:self.localCopyOfAllStatuses mappingsDictionary:@{ [NSNull null]: [ObjectMappings FoodTruckArticleMapping] }];
    mapperOperation.mappingOperationDataSource = mappingDS;
    NSError *error = nil;
    [mapperOperation execute:&error];
    [store.persistentStoreManagedObjectContext save:&error];
    [store.persistentStoreManagedObjectContext saveToPersistentStore:&error];
}
0
votes

Currently you're explicitly creating managed object instances in your loop so RestKit doesn't have an opportunity to check for duplicates. Instead, you should give RestKit the entire JSON response and let it do all of the mapping and object creation.

To achieve this, look at using RKManagedObjectResponseMapperOperation. This operation allows you to specify the managed object context, the mappings and the entire response content (you don't explicitly do any looping). This gives RestKit all of the information it needs to check for duplicates.

Something along the lines of:

RKManagedObjectResponseMapperOperation *responseMapperOperation = [[RKManagedObjectResponseMapperOperation alloc] initWithRequest:self.request
                                                                                                                         response:self.response
                                                                                                                             data:self.responseData
                                                                                                              responseDescriptors:self.responseDescriptors];
responseMapperOperation.mapperDelegate = self;
responseMapperOperation.managedObjectContext = self.managedObjectContext;
responseMapperOperation.managedObjectCache = self.managedObjectCache;

[responseMapperOperation setDidFinishMappingBlock:^(RKMappingResult *mappingResult, NSError *responseMappingError) {

    // use the mappingResult which contains the received objects

}];

Add the responseMapperOperation to a queue to execute.

Be sure to import the required RestKit classes.