iOS app requires deleting to fetch data - ios

i'm looking for some information regarding this problem.
I have an iOS project which fetches data from a remote server using REST-Kit. If I open the iOS simulator, delete the app, and then build and run, the tableview appears as expected. Left Image
However if I then rebuild and run without deleting the app within the simulator first, there are no thrown errors, but the cells in the tableview are empty. Right Image
Has anybody had this issue before? How do I get the data to appear correctly without first deleting the app! Thanks for reading!
Code from app delegate to initialise core data and define mappings between JSON and core data.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSLog(#"App started");
// Initialise RestKit
NSURL *baseURL = [NSURL URLWithString:#"http://*.*.*.*/"];
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:baseURL];
// Initialize managed object model from bundle
NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
// Initialize managed object store
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
NSError *error = nil;
BOOL success = RKEnsureDirectoryExistsAtPath(RKApplicationDataDirectory(), &error);
if(! success){
RKLogError(#"Failed to create application directory at path '%#': %#", RKApplicationDataDirectory(), error);
}
[objectManager setAcceptHeaderWithMIMEType:RKMIMETypeJSON];
// Complete core data store initialization
[managedObjectStore createPersistentStoreCoordinator];
NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"EventsDB.sqlite"];
NSString *seedPath = [[NSBundle mainBundle] pathForResource:#"RKSeedDatabase" ofType:#"sqlite"];
NSPersistentStore *persistantStore = [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:seedPath withConfiguration:nil options:nil error:&error];
NSAssert(persistantStore, #"Failed to add persistent with error %#", error);
objectManager.managedObjectStore = managedObjectStore;
// Create the managed object contexts
[managedObjectStore createManagedObjectContexts];
// Configure a managed object cache to ensure we don't create duplicate objects
managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
// If a data request fails we should send an NSError message
RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[RKErrorMessage class]];
[errorMapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"errorMessage"]];
RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:errorMapping pathPattern:nil keyPath:#"error" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
[objectManager addResponseDescriptorsFromArray:#[errorDescriptor]];
// Map event
RKEntityMapping *eventMapping = [RKEntityMapping mappingForEntityForName:#"Event" inManagedObjectStore:managedObjectStore];
eventMapping.identificationAttributes = #[#"eventId"];
[eventMapping addAttributeMappingsFromDictionary:#{//#"JSON":#"MODEL"
#"_id":#"eventId",
#"name":#"name",
#"venue":#"venue",
#"drink_prices":#"prices",
#"popularity":#"popularity",
#"genre":#"genre",
#"promotionQuote":#"promotionQuote",
#"dress_code":#"dressCode",
#"opening_time":#"openingTime",
#"closing_time":#"closingTime",
#"critic_review":#"criticReview",
#"critic_rating":#"criticRating",
#"image":#"imageURL",
#"published":#"published"
}];
// Map drink deals
RKEntityMapping *dealMapping = [RKEntityMapping mappingForEntityForName:#"Deal" inManagedObjectStore:managedObjectStore];
dealMapping.identificationAttributes = #[#"dealId"];
[dealMapping addAttributeMappingsFromDictionary:#{ //#"JSON":#"MODEL"
#"_id":#"dealId",
#"description":#"dealDescription",
#"price":#"price"
}];
// Map entries
RKEntityMapping *entryMapping = [RKEntityMapping mappingForEntityForName:#"Entry" inManagedObjectStore:managedObjectStore];
entryMapping.identificationAttributes = #[#"entryId"];
[entryMapping addAttributeMappingsFromDictionary:#{ //#"JSON":#"MODEL"
#"_id":#"entryId",
#"type":#"type",
#"price":#"price"
}];
// Map guestliest -- reviews, city and university come off a guestlisted user
RKEntityMapping *guestlistMapping = [RKEntityMapping mappingForEntityForName:#"User" inManagedObjectStore:managedObjectStore];
guestlistMapping.identificationAttributes = #[#"userId"];
[guestlistMapping addAttributeMappingsFromDictionary:#{//#"JSON":#"MODEL"
#"_id":#"userId",
#"name":#"name",
}];
// Map vip extras
RKEntityMapping *vipMapping = [RKEntityMapping mappingForEntityForName:#"VIP" inManagedObjectStore:managedObjectStore];
vipMapping.identificationAttributes = #[#"vipId"];
[vipMapping addAttributeMappingsFromDictionary:#{ //#"JSON":#"MODEL"
#"_id":#"vipId",
#"type":#"type",
#"available":#"available"
}];
// EventList Mapping
RKEntityMapping *eventListMapping = [RKEntityMapping mappingForEntityForName:#"EventList" inManagedObjectStore:managedObjectStore];
// Register relationships
[eventListMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"events" toKeyPath:#"events" withMapping:eventMapping]];
[eventMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"drink_deals" toKeyPath:#"deals" withMapping:dealMapping]];
[eventMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"entry" toKeyPath:#"entries" withMapping:entryMapping]];
[eventMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"guestlist" toKeyPath:#"guestlist" withMapping:guestlistMapping]];
[eventMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"vipExtras" toKeyPath:#"vipExtras" withMapping:vipMapping]];
// Regiseter event mapping with the provider. Do i have to do this for all 4 event maps?
RKResponseDescriptor *eventListResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:eventListMapping
method:RKRequestMethodGET
pathPattern:#"api/event.json"
keyPath:nil
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:eventListResponseDescriptor];
NSLog(#"objMan responseDescriptors->%#", objectManager.responseDescriptors);
// enable activity indicator spinner
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
// Map
return YES;
}
Code to fetch data
-(void)requestData {
NSLog(#"requesting data");
NSString *requestPath = #"api/event.json";
[[RKObjectManager sharedManager]
getObjectsAtPath:requestPath
parameters:nil
success: ^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
// events have been saved in core data by now
NSLog(#"Success");
[self fetchEventsFromContext];
}
failure: ^(RKObjectRequestOperation *operation, NSError *error){
RKLogError(#"Load failed with error: %#", error);
NSLog(#"Fail");
}
];
}
-(void)fetchEventsFromContext {
NSManagedObjectContext *context = [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"EventList"];
if(!context) exit(-1);
if(!fetchRequest) exit(-1);
//NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:#"popularity" ascending:YES];
fetchRequest.sortDescriptors = nil;//#[descriptor];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
EventList *eventList = [fetchedObjects firstObject];
self.events = [eventList.events allObjects];
if(!self.events){NSLog(#"No events");}
[self.tableView reloadData];
NSLog(#"reloading data");
}
**Code to render table cells **
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"rendering cells");
MasterTableCellTableViewCell *cell = (MasterTableCellTableViewCell *)[tableView dequeueReusableHeaderFooterViewWithIdentifier:#"MasterTableCell" ];
if(!cell)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MasterTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSLog(#"Got here");
NSLog(#"self.events[indexPath.row]->%#", self.events[indexPath.row]);
tableView.rowHeight = 160;
Event *event = self.events[indexPath.row];
NSString *imageURL = event.imageURL;
[cell.backgroundImageView sd_setImageWithURL:[NSURL URLWithString:imageURL]];
cell.eventLocationLabel.text = event.venue;
cell.eventNameLabel.text = event.name;
cell.criticRatingLabel.text = #"Critic Rating";
// cell.criticRatingValue. = event.criticRating; // How to display a number?
cell.userRatingLabel.text = #"User Rating";
cell.userRatingValue.text = #"X";
NSLog(#"Finished assigning cell deets");
return cell;
}

Related

RestKit Mapping Invalid parameter not satisfying: managedObjectStore

Possible duplication : RestKit Core Data 'managedObjectStore is nil'
Could somebody explain me why when I run my application I get error like :
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: managedObjectStore'
*** First throw call stack
It happened to me only in my MainViewController when I'm calling the following method:
-(void)loadLocationsOfWearers{
RKManagedObjectStore *store = [[DateModel sharedDataModel] objectStore];
NSIndexSet *statusCodeSet= RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful);
RKMapping *mapping = [MappingProvider watchesMapping];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping method:RKRequestMethodAny pathPattern:nil keyPath:#"watch" statusCodes:statusCodeSet];
NSURL *url = [NSURL URLWithString:kSERVER_ADDR];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
RKManagedObjectRequestOperation *operation =[[RKManagedObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[responseDescriptor]];
operation.managedObjectCache = store.managedObjectCache;
operation.managedObjectContext = store.mainQueueManagedObjectContext;
[operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
_wearerList = mappingResult.array;
NSLog(#"Results:\n %#",mappingResult.dictionary);
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"ERROR: %#", error);
NSLog(#"Response: %#", operation.HTTPRequestOperation.responseString);
}];
[operation start];
}
When I'm calling this method in other ViewControllers everything works fine.
It looks like my managedObjectStore is nil, but I don't know what is the reason for that...
Please take a look at my DateModel code and share with me your ideas:
#implementation DateModel
+ (id)sharedDataModel
{
static DateModel *__sharedDataModel = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__sharedDataModel = [[DateModel alloc] init];
});
return __sharedDataModel;
}
- (NSManagedObjectModel *)managedObjectModel
{
return [NSManagedObjectModel mergedModelFromBundles:nil];
}
- (id)optionsForSqliteStore
{
return #{
NSInferMappingModelAutomaticallyOption: #YES,
NSMigratePersistentStoresAutomaticallyOption: #YES
};
}
- (void)setup
{
self.objectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSString *path = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"Data.sqlite"];
NSLog(#"Setting up store at %#", path);
NSError *error;
[self.objectStore addSQLitePersistentStoreAtPath:path
fromSeedDatabaseAtPath:nil
withConfiguration:nil
options:[self optionsForSqliteStore]
error:&error];
[self.objectStore createManagedObjectContexts];
self.objectStore.managedObjectCache =[[RKInMemoryManagedObjectCache alloc]initWithManagedObjectContext:self.objectStore.persistentStoreManagedObjectContext];
[RKManagedObjectStore setDefaultStore:self.objectStore];
}
In my AppDelegate method I just simply setup my store.
-(void)setupCoreData
{
[[DateModel sharedDataModel]setup];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self setupCoreData];
}
When I run my application I'm getting error like on the screen below:
+(RKMapping *)watchesMapping
{
RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:#"Watches" inManagedObjectStore:[[DateModel sharedDataModel]objectStore]];
[mapping addAttributeMappingsFromDictionary:#{
#"id": #"watch_id",
#"altitude":#"altitude",
#"battery_life":#"battery_life",
#"button_press_time":#"button_press_time",
#"charging_status":#"charging_status",
#"gmaps":#"gmaps",
#"id_addr":#"id_addr",
#"last_keep_alive":#"last_keep_alive",
#"last_update_time":#"last_update_time",
#"latitude":#"latitude",
#"longitude":#"longitude",
#"location":#"location",
#"network":#"network",
#"phonewatchno":#"phonewatchno",
#"rssi":#"rssi",
#"short_imei":#"short_imei",
#"updated_at":#"updated_at",
#"voltage":#"voltage",
#"waerer_id":#"wearer_id",
#"updated_at":#"updated_at",
#"token":#"token"
}
];
[mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"alerts" toKeyPath:#"alerts" withMapping:[MappingProvider alertsMapping]]];
return mapping;
}
loadLocationsOfWearers is being called too soon (before setupCoreData is called). Call it later, or call setupCoreData when the data model singleton is created.

How to store nested JSON object into Core Data using RestKit

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.
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.

POST with RestKit 0.20 - NSManagedObjectContext issue

I'm trying to make a POST request with RestKit, but when I call the postObject method of my RKObjectManager I get the following error :
Can only use -performBlockAndWait: on an NSManagedObjectContext that was created with a queue.
This error came when the method performBlockAndWait is called by the RKManagedObjectRequestOperation class.
Here is my code :
RKObjectMapping *responseMapping = [RKObjectMapping requestMapping];
[responseMapping addAttributeMappingsFromDictionary:#{
// ...
}];
NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful); // Anything in 2xx
NSString *pathPattern = #"myPath";
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:responseMapping method:RKRequestMethodPOST pathPattern:pathPattern keyPath:nil statusCodes:statusCodes];
RKObjectMapping *requestMapping = [RKObjectMapping requestMapping];
[requestMapping addAttributeMappingsFromDictionary:#{
// ...
}];
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:requestMapping objectClass:[MyClass class] rootKeyPath:#"" method:RKRequestMethodPOST];
NSURL *baseURL = [NSURL URLWithString:#"http://192.168.1.1:8080"];
RKObjectManager *manager = [RKObjectManager managerWithBaseURL:baseURL];
[manager addRequestDescriptor:requestDescriptor];
[manager addResponseDescriptor:responseDescriptor];
NSManagedObjectModel *managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
manager.managedObjectStore = managedObjectStore;
[managedObjectStore createPersistentStoreCoordinator];
NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"mySqliteDatabaseFile"];
NSError *error;
NSPersistentStore *persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error];
NSAssert(persistentStore, #"Failed to add persistent store with error: %#", error);
[managedObjectStore createManagedObjectContexts];
managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
// POST to create
#try {
[manager postObject:visitReport path:#"myPath" parameters:nil success:^( RKObjectRequestOperation *operation , RKMappingResult *mappingResult ) {
NSLog(#"Success");
} failure:^( RKObjectRequestOperation *operation , NSError *error ){
NSLog(#"Failure - %#",error);
}];
}
#catch (NSException *exception) {
NSLog(#"error - %#", exception);
}
I don't understand the problem with my context, any help is welcomed.
Thank you.
Ok finally I find the problem :
The object visitReport I tried to POST is not from the NSManagedObjectContext expected by RestKit.
To solve, I create a new object :
MyObject *visitReportContext = [NSEntityDescription insertNewObjectForEntityForName:#"tableName"
inManagedObjectContext:objectStore.mainQueueManagedObjectContext];
visitReportContext.Id = visitReport.Id
visitReportContext.title = visitReport.title
// etc..
Then, when I POST my new object, it works. But it's not very beautiful... How can I deal with my old object visitReport without create a new one in this "RestKit context" ?

iOS RestKit issue: Invalid parameter not satisfying: responseDescriptors

I am trying to use RestKit to retrieve a listing of events and I keep getting this:
2013-05-20 10:52:56.708 EventApp[3380:c07] I restkit:RKLog.m:34 RestKit logging initialized...
2013-05-20 10:52:56.773 EventApp[3380:c07] *** Assertion failure in -[RKObjectRequestOperation initWithRequest:responseDescriptors:], /Users/mitchell/Desktop/eventapp/take2/EventApp/Pods/RestKit/Code/Network/RKObjectRequestOperation.m:158
2013-05-20 10:52:56.774 EventApp[3380:c07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: responseDescriptors'
I have been scratching my head for days on this one. As I have a fair amount of gaps in my iOS dev skills(about one project every year) it would greatly help if someone can just lead me in the right direction here using some laymen terms.
Please consider that I am looking to use enqueueObjectRequestOperation specifically for batching multiple requests. I have just pieced together bits of my code for translation here.
Here is what my datamodel looks like:
Here is the what the JSON file looks like:
[{
"id":1,
"farm_id":1,
"all_day": "NO",
"from": "2013-05-08T18:45:38Z",
"to": "2013-05-08T18:45:38Z",
"name": "event 1",
"desc": "some description",
"photo": "some.png",
"price": "price"
}]
Here is my code:
NSManagedObjectContext *context;
RKObjectManager *objectManager;
if (self.eventContext == nil) {
NSError *error = nil;
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"FarmApp" ofType:#"momd"]];
RKEntityMapping *entityMapping;
// NOTE: Due to an iOS 5 bug, the managed object model returned is immutable.
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
// Initialize the Core Data stack
[managedObjectStore createPersistentStoreCoordinator];
NSPersistentStore __unused *eventPersistentStore = [managedObjectStore addInMemoryPersistentStore:&error];
NSAssert(eventPersistentStore, #"Failed to add persistent store: %#", error);
[managedObjectStore createManagedObjectContexts];
// Set the default store shared instance
[RKManagedObjectStore setDefaultStore:managedObjectStore];
// Configure the object manager
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"http://sandbox.bm.com"]];
[RKObjectManager setSharedManager:objectManager];
[objectManager setRequestSerializationMIMEType:#"application/json"];
[objectManager setAcceptHeaderWithMIMEType:#"text/plain"];
objectManager.managedObjectStore = managedObjectStore;
entityMapping = [RKEntityMapping mappingForEntityForName:#"Event" inManagedObjectStore:managedObjectStore];
[entityMapping addAttributeMappingsFromDictionary:#{
#"id": #"eventID",
#"farm_id": #"farm",
#"all_day": #"allDay",
#"from": #"from",
#"to": #"to",
#"name": #"name",
#"desc": #"desc",
#"photo": #"photo",
#"price": #"price"
}];
RKResponseDescriptor *successDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:entityMapping pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:successDescriptor];
RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:entityMapping pathPattern:nil keyPath:#"errors" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
[objectManager addResponseDescriptor:errorDescriptor];
self.eventContext = managedObjectStore.mainQueueManagedObjectContext;
}
context = self.eventContext;
NSString* url = [NSString stringWithFormat:#"http://sandbox.bm.com/farmapp/%#.json", #"events"];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:[objectManager responseDescriptors]];
[operation setCompletionBlockWithSuccess:nil failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Loaded this error: %#", [error localizedDescription]);
}];
NSArray *operations = [NSArray arrayWithObjects:operation, nil];
for (int i = 0; i < [operations count]; i++ ) {
[[RKObjectManager sharedManager] enqueueObjectRequestOperation:[operations objectAtIndex:i]];
}
Can someone out there help me?
Here is the final solution
if (self.eventContext == nil) {
NSManagedObjectContext *context;
RKObjectManager *objectManager;
NSError *error = nil;
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"FarmApp" ofType:#"momd"]];
RKEntityMapping *entityMapping;
// NOTE: Due to an iOS 5 bug, the managed object model returned is immutable.
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
// Initialize the Core Data stack
[managedObjectStore createPersistentStoreCoordinator];
NSPersistentStore __unused *eventPersistentStore = [managedObjectStore addInMemoryPersistentStore:&error];
NSAssert(eventPersistentStore, #"Failed to add persistent store: %#", error);
[managedObjectStore createManagedObjectContexts];
// Set the default store shared instance
[RKManagedObjectStore setDefaultStore:managedObjectStore];
// Configure the object manager
objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"http://sandbox.bm.com"]];
[RKObjectManager setSharedManager:objectManager];
[objectManager setRequestSerializationMIMEType:#"application/json"];
[objectManager setAcceptHeaderWithMIMEType:#"text/plain"];
objectManager.managedObjectStore = managedObjectStore;
entityMapping = [RKEntityMapping mappingForEntityForName:#"Event" inManagedObjectStore:managedObjectStore];
[entityMapping addAttributeMappingsFromDictionary:#{
#"id": #"eventID",
//#"farm_id": #"farm",-->cannot create relationship this way
#"farm_id" : #"farmID",//farmID attribute needs to be added to Event's model
#"all_day": #"allDay",
#"from": #"from",
#"to": #"to",
#"name": #"name",
#"desc": #"desc",
#"photo": #"photo",
#"price": #"price"
}];
[entityMapping addConnectionForRelationship:#"farm" connectedBy:#"farmID"];
RKResponseDescriptor *successDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:entityMapping pathPattern:nil keyPath:#"events" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:successDescriptor];
RKResponseDescriptor *errorDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:entityMapping pathPattern:nil keyPath:#"errors" statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)];
[objectManager addResponseDescriptor:errorDescriptor];
self.eventContext = managedObjectStore.mainQueueManagedObjectContext;
context = self.eventContext;
NSString* url = [NSString stringWithFormat:#"http://sandbox.bm.com/farmapp/%#.json", #"events"];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
RKManagedObjectRequestOperation *operation = [objectManager managedObjectRequestOperationWithRequest:request managedObjectContext:managedObjectStore.mainQueueManagedObjectContext success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failure");
}];
NSArray *operations = [NSArray arrayWithObjects:operation, nil];
for (int i = 0; i < [operations count]; i++ ) {
[[RKObjectManager sharedManager] enqueueObjectRequestOperation:[operations objectAtIndex:i]];
}
}
As #JoelH. suggests in one of his comments, you need to use RKManagedObjectRequestOperation instead of RKObjectRequestOperation.
For example :
RKManagedObjectRequestOperation *operation = [objectmanager managedObjectRequestOperationWithRequest:request managedObjectContext:managedObjectStore.mainQueueManagedObjectContext success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
NSLog(#"Success");
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"Failure");
}];
Besides, I think the way you are mapping
#"farm_id": #"farm"
is not correct.
If you want to build the relationship between Event and Farm, you need to use one of these methods :
relationshipMappingFromKeyPath:toKeyPath:withMapping:
or addConnectionForRelationship:connectedBy:
If the Farm object already exists and you only want to map the new Event, I would go for addConnectionForRelationship:connectedBy:
For example :
RKEntityMapping* eventMapping = [RKEntityMapping mappingForEntityForName:#"Event" inManagedObjectStore:managedObjectStore];
[entityMapping addAttributeMappingsFromDictionary:#{
#"id": #"eventID",
//#"farm_id": #"farm",-->cannot create relationship this way
#"farm_id" : #"farmID",//farmID attribute needs to be added to Event's model
#"all_day": #"allDay",
#"from": #"from",
#"to": #"to",
#"name": #"name",
#"desc": #"desc",
#"photo": #"photo",
#"price": #"price"
}];
[eventMapping addConnectionForRelationship:#"farm" connectedBy:#"farmID"];
It would also require to add a farmID attribute in your Event model, as RestKit does not allow relationship connection without intermediary attributes yet.
The RestKit docs (Mapping Without KVC) seem to imply that if you don't have a KVC label on the top level(e.g. events:[]), a pathPattern: is required for the parser to know which mapping to use. Since both your keyPath: and pathPattern: are nil for your successDescriptor, I have two suggestions:
If changing the JSON structure is possible, change the top level to { events:[...] } and change keyPath:nil to keyPath:#"events" to reflect this change.
If it's not possible, change pathPattern:nil to pathPattern:#"/farmapp" Notice, I set pathPattern: to match your resource URL. This second suggestion might not work if you have other types of resources branching from that URL as well.
Also, I noticed that your errorDescriptor uses the same mapping(entityMapping) as your successDescriptor. I don't know if that's what you intended, but if your error response is supposed to be some different error object, I suggest changing that.
Hope this helps!

RestKit: How to batch multiple requests and get a response once they finish?

I just found out RestKit and it will be an important part of the app I'm doing. At the time, I was able to integrate it with the core data, but have not figured out the best way to send multiple GET requests.
What I need to do is:
Get data from the following addresses:
http://url.com/api/banner/
http://url.com/api/category/
http://url.com/api/link/
The URL will always be in the following format: http://url.com/api/SOMETHING/
Once all requests are finished, I would like to run a code (such as calling a new view controller). What would be the best way to do this?
At the moment, this is the code I'm using:
- (id)init
{
self = [super init];
if (self) {
[self setupConnector];
[self setupDatabase];
[self setupMappings];
[self sendRequests];
}
return self;
}
- (void)setupConnector
{
// Initialize RestKIT
RKObjectManager *objectManager = [RKObjectManager managerWithBaseURL:[NSURL URLWithString:#"http://baseURL"]];
self.managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:[[NLCoreData shared] managedObjectModel]];
objectManager.managedObjectStore = self.managedObjectStore;
}
- (void)setupDatabase
{
NSString *storePath = [[NLCoreData shared] storePath];
NSError *error = nil;
NSPersistentStore *persistentStore = [self.managedObjectStore addSQLitePersistentStoreAtPath:storePath fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error];
NSAssert(persistentStore, #"Failed to add persistent store with error: %#", error);
[self.managedObjectStore createManagedObjectContexts];
self.managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:self.managedObjectStore.persistentStoreManagedObjectContext];
}
- (void)setupMappings
{
RKObjectManager *objectManager = [RKObjectManager sharedManager];
// Mappings
// banner
RKEntityMapping *bannerMapping = [RKEntityMapping mappingForEntityForName:#"Banner" inManagedObjectStore:self.managedObjectStore];
[bannerMapping addAttributeMappingsFromDictionary:#{
#"title": #"title",
#"id": #"bannerID",
#"created_at": #"created_at",
#"image": #"image",
#"resource_uri": #"resource_uri",
#"updated_at": #"updated_at",
#"url": #"url"
}];
bannerMapping.identificationAttributes = #[ #"bannerID" ];
RKResponseDescriptor *bannerDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:bannerMapping
pathPattern:#"/api/v1/banner/"
keyPath:#"objects"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:bannerDescriptor];
// category
RKEntityMapping *categoryMapping = [RKEntityMapping mappingForEntityForName:#"Category" inManagedObjectStore:self.managedObjectStore];
[categoryMapping addAttributeMappingsFromDictionary:#{
#"name": #"name",
#"id": #"categoryID",
#"created_at": #"created_at",
#"resource_uri": #"resource_uri",
#"updated_at": #"updated_at",
#"active": #"active"
}];
categoryMapping.identificationAttributes = #[ #"categoryID" ];
RKResponseDescriptor *categoryDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:categoryMapping
pathPattern:#"/api/v1/category/"
keyPath:#"objects"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
[objectManager addResponseDescriptor:categoryDescriptor];
}
- (void)sendRequests
{
RKObjectManager *objectManager = [RKObjectManager sharedManager];
// Send Request
[objectManager getObjectsAtPath:#"/api/v1/banner/" parameters:nil success:^(RKObjectRequestOperation * operation, RKMappingResult *mappingResult) {
NSLog(#"SUCCESS: %#", mappingResult.array);
} failure: ^(RKObjectRequestOperation * operation, NSError * error) {
NSLog(#"FAILURE %#", error);
}];
// category
[objectManager getObjectsAtPath:#"/api/v1/category/" parameters:nil success:^(RKObjectRequestOperation * operation, RKMappingResult *mappingResult) {
NSLog(#"SUCCESS: %#", mappingResult.array);
} failure: ^(RKObjectRequestOperation * operation, NSError * error) {
NSLog(#"FAILURE %#", error);
}];
}
Any tips?
The RestKit solution would be this: instead of using the convenience method ObjectManager::getObjectsAtPath you will have to init all of your RKObjectRequestOperations manually and then use ObjectManager::enqueueBatchOfObjectRequestOperations:progress:completion: method to enqueue them.
Alternatively, and I think this is actually easier and cleaner solution, use dispatch groups as described in the accepted answer to this question.
NSURL *url1 = [NSURL URLWithString:#"http://baseURL.domain/api/banner/"];
NSMutableURLRequest *request2 = [NSMutableURLRequest requestWithURL:url1];
RKObjectRequestOperation *objectRequestOperation1 = [[RKObjectRequestOperation alloc] initWithRequest:request2 responseDescriptors:#[ ResponseDescriptor ]];
NSURL *url2 = [NSURL URLWithString:#"http://baseURL.domain/api/category/"];
NSMutableURLRequest *request2 = [NSMutableURLRequest requestWithURL:url2];
RKObjectRequestOperation *objectRequestOperation2 = [[RKObjectRequestOperation alloc] initWithRequest:request2 responseDescriptors:#[ ResponseDescriptor ]];
NSArray *requestArray = [NSArray arrayWithObjects:objectRequestOperation,objectRequestOperation1,objectRequestOperation2, nil];
[[RKObjectManager sharedManager] enqueueBatchOfObjectRequestOperations:requestArray progress:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
//
// Handle process indicator
//
NSLog(#"%lu",(unsigned long)totalNumberOfOperations);
} completion:^(NSArray *operations) {
//
// Remove blocking dialog, do next tasks
?

Resources