saif
saif

Reputation: 1

RestKit - 'Cannot add object with entity 'entityName' to cache for entity of 'entityName''

I have iPad Application with three view controllers in uitabbarcontroller. In each view controller, I am calling different web service and mapping them to core data entity by using RestKit and then displaying the data in uitableview with nsfetchedresultscontroller. More information about my implementation is here.

First time when I load the data, the data is loaded and mapped correctly on every view controller but in one of the view controller when I try to reload the data in view controller-2 after loading data in view controller-3, the application is crashing with the below error:

2015-11-07 00:17:45.205 PharmacyStockTake[193:3854] T restkit.network:RKResponseMapperOperation.m:504 Mapping HTTP response to nil target object... 2015-11-07 00:17:45.206 PharmacyStockTake[193:3854] I restkit.core_data:RKInMemoryManagedObjectCache.m:94 Caching instances of Entity 'RackStockTakeStatus' by attributes 'stockTakeLocId' 2015-11-07 00:17:45.213 PharmacyStockTake[193:3854] * Assertion failure in -[RKEntityByAttributeCache addObjects:completion:], /Users/saif/Documents/iDev/ComPrjs/PharmacyStockTake/Pods/RestKit/Code/CoreData/RKEntityByAttributeCache.m:333 2015-11-07 00:17:45.214 PharmacyStockTake[193:3854] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot add object with entity 'RackStockTakeStatus' to cache for entity of 'RackStockTakeStatus'' *** First throw call stack: (0x2201585b 0x33a5adff 0x22015731 0x22da6ddb 0x14b67b 0x21d9034b 0x21d9022d 0x14b175 0x15a9e1 0x21d9034b 0x21d9022d 0x15a71d 0x161327 0x17bfb9 0x17a769 0x220091e9 0x21f8bbdb 0x17a65b 0x17c21b 0x17c9cd 0x17d271 0x22d283cf 0x1c3925 0x21d9034b 0x422d03 0x42c4fb 0x21d90247 0x1c26bb 0x1c072d 0x22d283cf 0x22dd682d 0x42d61b 0x425f53 0x42eb0f 0x42e961 0x34314e0d 0x343149fc) libc++abi.dylib: terminating with uncaught exception of type NSException

Here is my code:

Initializing RestKit in AppDelegate:

RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:baseURL];
    
    //[RKObjectManager setSharedManager:objectManager];
    [RKObjectManager setSharedManager:objectManager];
    
    
    // Initialize managed object model from bundle
    NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    
    // Initialize managed object store
    RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
    objectManager.managedObjectStore = managedObjectStore;
    
    // Complete Core Data stack initialization
    [managedObjectStore createPersistentStoreCoordinator];
    NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"StockTakeDB.sqlite"];
    NSString *seedPath = [[NSBundle mainBundle] pathForResource:@"StoreItemsDB" ofType:@"sqlite"];
    NSError *error;
    NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:seedPath withConfiguration:nil options:nil error:&error];
    NSAssert(persistentStore, @"Failed to add persistent store with error: %@", error);
    
    // Create the managed object contexts
    [managedObjectStore createManagedObjectContexts];
    
    // Configure a managed object cache to ensure we do not create duplicate objects
    managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];

RestKit Initialization and configuration code in View-Controller-2:

-(void)initTakeStockRestKit
{
    takeStockLocationWithStatusRequestPath = @"/stocktake/stocktake/1/usr/1/locwithstatus";
    RKObjectManager *objectManager = [RKObjectManager sharedManager];
    
    [[NSURLCache sharedURLCache] removeAllCachedResponses];

    // Initialize managed object model from bundle
    NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    
    // Initialize managed object store
    RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
    objectManager.managedObjectStore = managedObjectStore;
    
    // Complete Core Data stack initialization
    [managedObjectStore createPersistentStoreCoordinator];
    NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"StockTakeDB.sqlite"];
    NSString *seedPath = [[NSBundle mainBundle] pathForResource:@"StoreItemsDB" ofType:@"sqlite"];
    NSError *error;
    NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:seedPath withConfiguration:nil options:nil error:&error];
    NSAssert(persistentStore, @"Failed to add persistent store with error: %@", error);
    
    // Create the managed object contexts
    [managedObjectStore createManagedObjectContexts];
    
    // Configure a managed object cache to ensure we do not create duplicate objects
    managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
    
    [objectManager addFetchRequestBlock:^NSFetchRequest *(NSURL *URL) {
        RKPathMatcher *pathMatcher = [RKPathMatcher pathMatcherWithPattern:takeStockLocationWithStatusRequestPath];
        
        NSDictionary *argsDict = nil;
        BOOL match = [pathMatcher matchesPath:[URL relativePath] tokenizeQueryStrings:NO parsedArguments:&argsDict];
        
        if (match) {
            NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"RackStockTakeStatus"];
            return fetchRequest;
        }
        return nil;
    }];

    RKEntityMapping *rackStockTakeStatusListMapping = [RKEntityMapping mappingForEntityForName:@"RackStockTakeStatus" inManagedObjectStore:managedObjectStore];
    rackStockTakeStatusListMapping.identificationAttributes = @[@"stockTakeLocId"];
    
    [rackStockTakeStatusListMapping addAttributeMappingsFromDictionary:
     @{
       @"stockTakeLocId" : @"stockTakeLocId",
       @"stockTakeUuid" : @"stockTakeUuid",
       @"locId" : @"locId",
       @"locName" : @"locName",
       @"status" : @"status",
       @"stockTakeByUser" : @"stockTakeByUser",
       @"stockTakeByUserId" : @"stockTakeByUserId",
       @"beginTime" : @"beginTime",
       @"percentCompleted" : @"percentCompleted"
       }
     ];
    
    RKResponseDescriptor *rackStockTakeStatusListResponseDescriptor =
    [RKResponseDescriptor responseDescriptorWithMapping:rackStockTakeStatusListMapping
                                                 method:RKRequestMethodGET
                                            pathPattern:@"/stocktake/stocktake/:id/usr/:id/locwithstatus"
                                                keyPath:nil
                                            statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)
     ];
    
    [objectManager addResponseDescriptor:rackStockTakeStatusListResponseDescriptor];
}

Data loading on View-Controller-2:

NSString *requestPath = [NSString stringWithFormat:@"/stocktake/stocktake/%@/usr/1/locwithstatus",[defaults objectForKey:@"loggedInUserSelectedStoreId"]];

[[RKObjectManager sharedManager]
     getObjectsAtPath:requestPath
     parameters:nil
     success: ^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
          NSManagedObjectContext *context = [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext;
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"RackStockTakeStatus" inManagedObjectContext:context];
   [request setEntity:entity];
    NSError *error;
    [context executeFetchRequest:request error:&error];
          NSError *error;
    if (![[self fetchedResultsController] performFetch:&error]) {
    }
         [self.tableView reloadData];
         NSLog(@"requestDataItemsForStore - Mapping Success");
     }
     failure: ^(RKObjectRequestOperation *operation, NSError *error) {
         RKLogError(@"Load failed with error: %@", error);
         NSLog(@"requestDataItemsForStore - Loading Failed");
     }
     ];

RestKit Initialization and configuration code in View-Controller-3:

RKObjectManager *objectManager = [RKObjectManager sharedManager];
    
    // Initialize managed object model from bundle
    NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    
    // Initialize managed object store
    RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
    objectManager.managedObjectStore = managedObjectStore;
    
    // Complete Core Data stack initialization
    [managedObjectStore createPersistentStoreCoordinator];
    NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:@"StockTakeDB.sqlite"];
    NSString *seedPath = [[NSBundle mainBundle] pathForResource:@"StoreItemsDB" ofType:@"sqlite"];
    NSError *error;
    NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:seedPath withConfiguration:nil options:nil error:&error];
    NSAssert(persistentStore, @"Failed to add persistent store with error: %@", error);
    
    // Create the managed object contexts
    [managedObjectStore createManagedObjectContexts];
    
    // Configure a managed object cache to ensure we do not create duplicate objects
    managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
    
    [objectManager addFetchRequestBlock:^NSFetchRequest *(NSURL *URL) {
        RKPathMatcher *pathMatcher = [RKPathMatcher pathMatcherWithPattern:itemsByLocationRequestPath];
        
        NSDictionary *argsDict = nil;
        BOOL match = [pathMatcher matchesPath:[URL relativePath] tokenizeQueryStrings:NO parsedArguments:&argsDict];
        
        if (match) {
            NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"ItemsByLocation"];
            return fetchRequest;
        }
        
        return nil;
    }];
    
        RKEntityMapping *itemsByLocationListMapping = [RKEntityMapping mappingForEntityForName:@"ItemsByLocation" inManagedObjectStore:managedObjectStore];
        itemsByLocationListMapping.identificationAttributes = @[@"itemId"];
    
        [itemsByLocationListMapping addAttributeMappingsFromDictionary:
         @{
           @"itemId" : @"itemId",
           @"itemName" : @"itemName",
           @"itemCode" : @"itemCode",
           @"uomCode" : @"uomCode",
           @"locId" : @"locId",
           @"locName" : @"locName",
           @"subLocId" : @"subLocId",
           @"subLocName" : @"subLocName",
           @"storeItemId" : @"storeItemId",
           @"stockTakeQtyId" : @"stockTakeQtyId",
           @"countedTime" : @"countedTime",
           @"countQty" : @"countQty",
           @"removed" : @"removed",
           @"remarks" : @"remarks"
           }
         ];
    
        RKResponseDescriptor *itemsByLocationListResponseDescriptor =
        [RKResponseDescriptor responseDescriptorWithMapping:itemsByLocationListMapping
                                                     method:RKRequestMethodGET
                                                pathPattern:@"/stocktake/stocktake/:id/loc/:id/usr/:id/items"
                                                    keyPath:nil
                                                statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)
         ];
    
        [objectManager addResponseDescriptor:itemsByLocationListResponseDescriptor];

Data Loading on View Controller-3:

    [[RKObjectManager sharedManager]
     getObjectsAtPath:itemsByLocationRequestPath
     parameters:nil
     success: ^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
         NSManagedObjectContext *context = [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext;
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"ItemsByLocation"];
    
    NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:@"subLocName" ascending:YES];
    fetchRequest.sortDescriptors = @[descriptor];
    
    NSError *error = nil;
    [context executeFetchRequest:fetchRequest error:&error];
          NSError *error;
    if (![[self fetchedResultsController] performFetch:&error]) {

    }
         [self.tableView reloadData];
         NSLog(@"requestDataItemsForStore - Mapping Success");
     }
     failure: ^(RKObjectRequestOperation *operation, NSError *error) {
         RKLogError(@"Load failed with error: %@", error);
         NSLog(@"requestDataItemsForStore - Loading Failed");
     }
     ];

Can you help to resolve this issue?

Upvotes: 0

Views: 212

Answers (1)

Wain
Wain

Reputation: 119031

This is a threading and / or a multiple context issue. Your entity cache is setup with one context but then you're using it from a different context (which is why the entities have the same name but are different).

It's possible that this is because you're setting up multiple different core data stacks (it looks like it from the code samples). In this case you should factor out all of the core data stack and mapping so creation code into a new class - a data manager. Pass the data manager to each of the view controllers so they can use it to download the content they need.

If it's a threading issue then turning on core data threading exceptions could help you find the cause.

Note that using FRCs in each view controller is fine, but that you should only have one main thread context that you set them all up with.

Upvotes: 1

Related Questions