0

I’m trying to implement what I believe to be a pretty simple use case, but I’m not sure how to correctly go about it.

When a particular screen loads in my app, I need to call a webservice which returns a nested JSON object, then parse all the data and save into a series of Core Data managed objects. Whenever the user goes leaves and goes back to that screen, I want to replace all the data currently in my Core Data store with new data coming back from the server.

That’s the high level overview. Seems pretty simple.

setupRestKit is called on app load:

+ (void)setupRestKit
{
    RKObjectManager *manager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:BASE_URL]];
    [RKMIMETypeSerialization registerClass:[RKNSJSONSerialization class] forMIMEType:@"application/vnd.collection+json"];

    NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"Mobile" ofType:@"momd"]];
    NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
    RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];

    [managedObjectStore createPersistentStoreCoordinator];

    NSString *documentPath = [NIAppDelegate applicationDocumentsDirectory].relativePath;
    NSError *error;
    [managedObjectStore addSQLitePersistentStoreAtPath:[NSString stringWithFormat:@"%@/Mobile.sqlite", documentPath]
                                fromSeedDatabaseAtPath:nil
                                     withConfiguration:nil
                                               options:nil
                                                 error:&error];

    [managedObjectStore createManagedObjectContexts];

    manager.managedObjectStore = managedObjectStore;

    NSArray *rkClasses = [self allNIRestKitClasses];
    NSArray *descriptors = [self allDescriptorsFromRKClasses:rkClasses];

    [manager addResponseDescriptorsFromArray:descriptors];
}

The NSArray *descriptors on line 25 is populated with an array of descriptors, which includes the following returned descriptor object:

+ (RKResponseDescriptor *)getDescriptor
{
    RKManagedObjectStore *store = [RKObjectManager sharedManager].managedObjectStore;

    RKEntityMapping *scheduleMapping = [RKEntityMapping mappingForEntityForName:@"Schedule" inManagedObjectStore:store];
    [scheduleMapping addAttributeMappingsFromDictionary:@{@"version": @"version",
                                                      @"href": @"href"}];

    RKEntityMapping *scheduleItemMapping = [RKEntityMapping mappingForEntityForName:@"ScheduleItem" inManagedObjectStore:store];
    [scheduleItemMapping addAttributeMappingsFromDictionary:@{@"href": @"href"}];

    RKEntityMapping *scheduleDataMapping = [RKEntityMapping mappingForEntityForName:@"ScheduleData" inManagedObjectStore:store];
    [scheduleDataMapping addAttributeMappingsFromDictionary:@{@"AllCurricConfirmed": @"allCurricConfirmed",
                                                              @"EndDateTime": @"endDateTime",
                                                              @"EventLocation": @"eventLocation",
                                                              @"Notes": @"notes",
                                                              @"RecordID": @"recordID",
                                                              @"ScheduleType": @"scheduleType",
                                                              @"StartDateTime": @"startDateTime",
                                                              @"Title": @"title"}];

    RKEntityMapping *scheduleLinkMapping = [RKEntityMapping mappingForEntityForName:@"ScheduleLink" inManagedObjectStore:store];
    [scheduleLinkMapping addAttributeMappingsFromDictionary:@{@"rel": @"rel",
                                                              @"href": @"href"}];

    RKRelationshipMapping *scheduleLinksInScheduleItem = [RKRelationshipMapping relationshipMappingFromKeyPath:@"link"
                                                                                                     toKeyPath:@"scheduleLinks"
                                                                                                   withMapping:scheduleLinkMapping];
    [scheduleItemMapping addPropertyMapping:scheduleLinksInScheduleItem];

    RKRelationshipMapping *scheduleDatasInScheduleItem = [RKRelationshipMapping relationshipMappingFromKeyPath:@"data"
                                                                                                 toKeyPath:@"scheduleDatas"
                                                                                               withMapping:scheduleDataMapping];
    [scheduleItemMapping addPropertyMapping:scheduleDatasInScheduleItem];

    RKRelationshipMapping *scheduleItemsInSchedule = [RKRelationshipMapping relationshipMappingFromKeyPath:@"items"
                                                                                                 toKeyPath:@"scheduleItems"
                                                                                               withMapping:scheduleItemMapping];
    [scheduleMapping addPropertyMapping:scheduleItemsInSchedule];

    NSIndexSet *scheduleStatusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
    RKResponseDescriptor *scheduleDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:scheduleMapping
                                                                                         method:RKRequestMethodGET
                                                                                    pathPattern:nil
                                                                                        keyPath:@"collection"
                                                                                    statusCodes:scheduleStatusCodes];

    return scheduleDescriptor;
}

Those descriptors and relationships completely encompass my nested JSON object, and I believe them to work correctly.

My question, I guess, is with how RestKit and Core Data work together. I’m relatively new to both of these technologies. The following snippet describes what my ViewController is doing to get data from the webservice:

// ViewController.m
- (void)getData
{
    [SVProgressHUD showWithStatus:@"Downloading Schedule"];

    [NIRestKitSchedule getScheduleWithBlock:^(BOOL valid) {
        if (valid) {
            // get the data from Core Data (because I assume that RestKit has saved everything into it already?)
            NSManagedObjectContext *mainContext = RKObjectManager.sharedManager.managedObjectStore.mainQueueManagedObjectContext;
            NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:@"Schedule"];
            NSArray *schedule = [mainContext executeFetchRequest:fetch error:nil];

            // TODO: and do stuff with it

            [SVProgressHUD dismiss];
        } else {
            [SVProgressHUD showErrorWithStatus:@"Could not get Schedule"];
        }
    }];
}

// NIRestKitSchedule.m
+ (void)getScheduleWithBlock:(void (^)(BOOL))valid
{
    [self deleteEntity:@"Schedule"];

    NSURL *scheduleUrl = [RKObjectManager sharedManager].baseURL;

    scheduleUrl = [scheduleUrl URLByAppendingPathComponent:SCHEDULE_ENDPOINT];

    scheduleUrl = [scheduleUrl URLByAppendingDefaultParameters];
    [[RKObjectManager sharedManager] getObjectsAtPath:[scheduleUrl absoluteString]
                                           parameters:nil
                                              success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
                                                  valid(YES);
                                              }
                                              failure:^(RKObjectRequestOperation *operation, NSError *error) {
                                                  valid(NO);
                                              }];

}

+ (void)deleteEntity:(NSString *)entityName
{
    NSManagedObjectContext *mainContext = [RKObjectManager sharedManager].managedObjectStore.mainQueueManagedObjectContext;

    NSFetchRequest *fetchObjects = [[NSFetchRequest alloc] init];
    [fetchObjects setEntity:[NSEntityDescription entityForName:entityName inManagedObjectContext:mainContext]];
    [fetchObjects setIncludesPropertyValues:NO];

    NSError *error = nil;
    NSArray *objects = [mainContext executeFetchRequest:fetchObjects error:&error];

    for (NSManagedObject *object in objects) {
        [mainContext deleteObject:object];
    }
    NSError *saveError = nil;
    [mainContext save:&saveError];
}

Upon compiling the app and running for the first time, schedule in the last code snippet is correctly loaded with the nested array of data. But any subsequent calls to getData result in a Core Data “fault".

Should I be calling getObjectsAtPath:parameters:success:failure? Which contexts should I be storing and deleting my Schedule data into and from? Can someone point me in the right direction? RestKit is kind of overwhelming me right now.

4
  • When you say 'fault', you mean that is what is printed in a log statement? Commented Apr 16, 2014 at 14:38
  • @Wain correct: (lldb) po schedule <_PFArray 0x14dd6c80>( <Schedule: 0x14d6f190> (entity: Schedule; id: 0x14d32c60 <x-coredata://D88456A9-CBDD-4E92-B746-989C5E00B459/Schedule/p3> ; data: <fault>) ) Commented Apr 16, 2014 at 14:40
  • I think my lack of understanding is related to Managed Object Contexts. Commented Apr 16, 2014 at 14:45
  • The fault there just means the data hasn't been loaded into memory - not to worry about Commented Apr 16, 2014 at 14:47

1 Answer 1

1

Your code should generally be using mainQueueManagedObjectContext so that looks fine.

When you want to save, you should be calling saveToPersistentStore: instead of save: on the context.

Yes, you should be calling getObjectsAtPath:parameters:success:failure because that's what gets the data from the server.

You shouldn't need to delete objects yourself, check out fetch request blocks.

Sign up to request clarification or add additional context in comments.

2 Comments

Alright! Well calling saveToPersistentStore: instead of save: in my deleteEntity: method cleared up my Core Data fault, so thanks! Now I'm going to research fetch request blocks and how they relate to deleting managed objects.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.