Evan R
Evan R

Reputation: 875

Duplicate Core Data objects created in RestKit 0.20.2

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!

Upvotes: 3

Views: 2132

Answers (2)

Evan R
Evan R

Reputation: 875

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

Upvotes: 2

Wain
Wain

Reputation: 119031

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.

Upvotes: 0

Related Questions