I am fetching data from the New York Times Bestsellers JSON API using Reskit. I believe I have an issue with my attributes mapping. A typical JSON object that has to be fetched looks like the structure below. My code is also shown. The API call does return objects as matching the number of expected results but the RKMappingResult in the requestDataFromAPI method returns "[__NSCFBoolean length]: unrecognized selector sent to instance". I am not able to access them as printing Books gives nil. I am not sure what I'm doing wrong.
{
"status":"OK",
"copyright":"Copyright (c) 2016 The New York Times Company. All Rights Reserved.",
"num_results":10,
"last_modified":"2016-03-04T13:12:31-05:00",
"results":
{
"list_name":"Animals",
"bestsellers_date":"2016-02-27",
"published_date":"2016-03-13",
"display_name":"Animals",
"normal_list_ends_at":10,
"updated":"MONTHLY",
"books": [
{"rank":1,
"rank_last_week":0,
"weeks_on_list":0,
"asterisk":0,
"dagger":0,
"primary_isbn10":"0802123414",
"primary_isbn13":"9780802123411",
"publisher":"Grove Atlantic",
"description":"A grief-stricken British woman decides to raise a goshawk, a fierce bird that is notoriously difficult to tame.",
"price":0,
"title":"H IS FOR HAWK","author":"Helen Macdonald",
"contributor":"by Helen Macdonald",
"isbns": [
{"isbn10":"0802123414",
"isbn13":"9780802123411"
},
{"isbn10":"1448130727",
"isbn13":"9781448130726"
},
{"isbn10":"1481530968",
"isbn13":"9781481530965"
},
{"isbn10":"148153095X",
"isbn13":"9781481530958"
},
{"isbn10":"1410483614",
"isbn13":"9781410483614"
},
{"isbn10":"0802124739",
"isbn13":"9780802124739"
}]
}
}
- (void) initializeRestAPI
{
// Initialize RestKit using API base address
NSURL * baseURL = [NSURL URLWithString:#"http://api.nytimes.com"];
RKObjectManager * objectManager = [RKObjectManager managerWithBaseURL:baseURL];
// Initialize Core Data's managed object model from the bundle
NSManagedObjectModel * managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
// Initialize RestKit's managed object store
RKManagedObjectStore * managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
objectManager.managedObjectStore = managedObjectStore;
// Complete Core Data stack initialization via RestKit
[managedObjectStore createPersistentStoreCoordinator];
NSString * persistentStorePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"DataModel.sqlite"];
NSString * seedDatabasePath = [[NSBundle mainBundle] pathForResource:#"RKSeedDatabase" ofType:#"sqlite"];
NSError * error;
NSPersistentStore * persistentStore = [managedObjectStore addSQLitePersistentStoreAtPath:persistentStorePath fromSeedDatabaseAtPath:seedDatabasePath withConfiguration:nil options:nil error:&error];
NSAssert(persistentStore, #"Failed to add persistent store with error: %#", error);
// Create RestKit's managed object contexts
[managedObjectStore createManagedObjectContexts];
// Configure a managed object cache
managedObjectStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedObjectStore.persistentStoreManagedObjectContext];
[self setupEntityMappingForObjectStore:managedObjectStore withObjectManager:objectManager];
[self requestDataFromAPI];
}
-(void) setupEntityMappingForObjectStore: (RKManagedObjectStore *) managedObjectStore withObjectManager: (RKObjectManager *) objectManager
{
RKEntityMapping * bookListMapping = [RKEntityMapping mappingForEntityForName:#"BookList" inManagedObjectStore:managedObjectStore];
bookListMapping.identificationAttributes = #[#"listName"];
[bookListMapping addAttributeMappingsFromDictionary:
#{#"results.list_name": #"listName",
#"results.bestsellers_date": #"bestsellersDate",
#"results.published_date": #"publishedDate",
#"results.display_name": #"displayName",
#"results.normal_list_ends_at": #"normalListEndsAt",
#"results.updated": #"updated"
}];
RKEntityMapping * bookMapping = [RKEntityMapping mappingForEntityForName:#"Book" inManagedObjectStore:managedObjectStore];
bookMapping.identificationAttributes = #[#"title"];
[bookMapping addAttributeMappingsFromDictionary:
#{#"rank": #"rank",
#"rank_last_week": #"rankLastWeek",
#"weeks_on_list": #"weeksOnList",
#"primary_isbn10": #"primaryIsbn10",
#"primary_isbn13": #"primaryIsbn13",
#"amazon_product_url": #"productUrl",
#"book_image": #"bookImage",
#"publisher": #"publisher",
#"description": #"bookDescription",
#"title": #"title",
#"contributor": #"contributor",
#"author": #"author",
#"price": #"price"
}];
[bookListMapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:#"results.books" toKeyPath:#"books" withMapping:bookMapping]];
RKResponseDescriptor * bookListResponseDescriptor =
[RKResponseDescriptor responseDescriptorWithMapping:bookListMapping
method:RKRequestMethodGET
pathPattern:nil
keyPath:#"results.books"
statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)
];
[objectManager addResponseDescriptor:bookListResponseDescriptor];
// Enable Activity Indicator Spinner
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
}
- (void)fetchBooksFromContext
{
NSManagedObjectContext * context = [RKManagedObjectStore defaultStore].mainQueueManagedObjectContext;
NSFetchRequest * fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"BookList"];
NSSortDescriptor * descriptor = [NSSortDescriptor sortDescriptorWithKey:#"listName" ascending:YES];
fetchRequest.sortDescriptors = #[descriptor];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
BookList * bookList = [fetchedObjects firstObject];
NSArray * books = [bookList.books allObjects];
//NSArray * books = [fetchedObjects firstObject];
NSLog(#"Books: %#",books);
}
- (void)requestDataFromAPI
{
NSDictionary * apiKeyData = [[NSUserDefaults standardUserDefaults] objectForKey:#"apiKeyData"];
NSString * apiKey = [apiKeyData objectForKey:#"apiKeyData"];
NSLog(#"requestDataFromAPI apiKey: %#",apiKey);
NSString * requestPath = [[NSString alloc] initWithFormat:#"/svc/books/v3/lists/%#?&api-key=%#",_categoryListName, apiKey];
[[RKObjectManager sharedManager]
getObjectsAtPath:requestPath
parameters:nil
success: ^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult)
{
[self fetchBooksFromContext];
}
failure: ^(RKObjectRequestOperation *operation, NSError *error)
{
RKLogError(#"Loading from API failed with error: %#", error);
}
];
}
The core data object models are as shown
#import "BookList.h"
NS_ASSUME_NONNULL_BEGIN
#interface BookList (CoreDataProperties)
#property (nullable, nonatomic, retain) NSString *listName;
#property (nullable, nonatomic, retain) NSDate *bestsellersDate;
#property (nullable, nonatomic, retain) NSDate *publishedDate;
#property (nullable, nonatomic, retain) NSString *displayName;
#property (nullable, nonatomic, retain) NSNumber *normalListEndsAt;
#property (nullable, nonatomic, retain) NSString *updated;
#property (nullable, nonatomic, retain) NSSet<Book *> *books;
#end
#interface BookList (CoreDataGeneratedAccessors)
- (void)addBooksObject:(Book *)value;
- (void)removeBooksObject:(Book *)value;
- (void)addBooks:(NSSet<Book *> *)values;
- (void)removeBooks:(NSSet<Book *> *)values;
#end
NS_ASSUME_NONNULL_END
#import "Book.h"
NS_ASSUME_NONNULL_BEGIN
#interface Book (CoreDataProperties)
#property (nullable, nonatomic, retain) NSNumber *rankLastWeek;
#property (nullable, nonatomic, retain) NSNumber *weeksOnList;
#property (nullable, nonatomic, retain) NSString *primaryIsbn10;
#property (nullable, nonatomic, retain) NSString *primaryIsbn13;
#property (nullable, nonatomic, retain) NSString *productUrl;
#property (nullable, nonatomic, retain) NSString *bookImageUrl;
#property (nullable, nonatomic, retain) NSString *publisher;
#property (nullable, nonatomic, retain) NSString *bookDescription;
#property (nullable, nonatomic, retain) NSString *title;
#property (nullable, nonatomic, retain) NSString *contributor;
#property (nullable, nonatomic, retain) NSString *author;
#property (nullable, nonatomic, retain) NSNumber *price;
#end
NS_ASSUME_NONNULL_END
I have 2 model:
#interface Program : NSObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, strong) NSMutableArray *guides;
#end
#interface Guide : NSObject
#property (nonatomic, retain) NSString *name;
#end
And I add some guides to program from one xml:
Program *program = [Program new];
program.name = #"My list"
for(DDXMLElement *guideElement in [programElement nodesForXPath:#"guide" error:&error])
{
Guide *guide = [Guide new];
guide.name = [guideElement stringValue];// [p attribute:#"name"];
[program.guides addObject:guide];
NSLog(#"load guide number: %d", [program.guides count]);
}
The out is always "load guide number: 0"
program.guides is nil, since you never created it.
In your Program's init method, add:
self.guides = [[NSMutableArray alloc] init];
Or, more sloppily, before your for loop add:
program.guides = [[NSMutableArray alloc] init];
I had spent few hours try to insert/add object but duplicated records happened. Campaign record re-inserted even it already exists. Am I missing something?
Below are my codes:
Campaign.h
#interface Campaign : NSManagedObject
#property (nonatomic, strong) NSNumber* campaignId;
#property (nonatomic, strong) NSString* title;
#end
Card.h
#class Campaign;
#interface Card : NSManagedObject
#property (nonatomic, strong) NSNumber* cardId;
#property (nonatomic, strong) NSString* name;
#property (nonatomic, strong) Campaign* campaign;
#end
ViewController.m
...
Campaign* campaign = [Campaign object];
campaign.campaignId = [NSNumber numberWithInt:1];
campaign.title = #"Hello world";
Card* card = [Card object];
card.cardId = #"1234567890";
card.campaign = campaign;
[[[RKObjectManager sharedManager] objectStore] save:nil];
EDITED ViewController.m
...
RKManagedObjectMapping* cardMapping = [RKManagedObjectMapping mappingForClass:[Card class] inManagedObjectStore:[RKObjectManager sharedManager].objectStore];
[cardMapping mapKeyPath:#"id" toAttribute:#"cardId"];
[campaignMapping mapKeyPath:#"name" toAttribute:#"name"];
cardMapping.primaryKeyAttribute = #"cardId";
RKManagedObjectMapping* campaignMapping = [RKManagedObjectMapping mappingForClass:[Campaign class] inManagedObjectStore:[RKObjectManager sharedManager].objectStore];
[campaignMapping mapKeyPath:#"id" toAttribute:#"campaignId"];
[campaignMapping mapKeyPath:#"title" toAttribute:#"title"];
campaignMapping.primaryKeyAttribute = #"campaignId";
Campaign* campaign = [Campaign object];
campaign.campaignId = [NSNumber numberWithInt:1];
campaign.title = #"Hello world";
Card* card = [Card object];
card.cardId = #"1234567890";
card.campaign = campaign;
[[[RKObjectManager sharedManager] objectStore] save:nil];
Yes, add a .primaryKeyAttribute to your mapping.
This will do the pk stuff for you when you are importing data via rest kit. If you are just doing 'normal' core data stuff with Restkit, you need to deal with referential integtrity etc. yourself.
I can't figure out why I am getting the 'mutating method sent to immutable object' in this piece of code. The array must be immutable somehow but I don't know why.
Interface:
#interface SectionsViewController : UIViewController<UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate> {
UITableView *table;
UISearchBar *search;
NSMutableDictionary *names;
NSMutableArray *keys;
}
#property (nonatomic, retain) IBOutlet UITableView *table;
#property (nonatomic, retain) IBOutlet UISearchBar *search;
#property (nonatomic, retain) NSDictionary *allNames;
#property (nonatomic, retain) NSMutableDictionary *names;
#property (nonatomic, retain) NSMutableArray *keys;
-(void) resetSearch;
-(void) handleSearchForTerm:(NSString *)searchTerm;
#end
Notice that names is a MutableDictionary.
The following line is throwing the exception
[array removeObjectsInArray:toRemove];
Here the method in full context:
-(void)handleSearchForTerm:(NSString *)searchTerm
{
NSMutableArray *sectionsToRemove = [[NSMutableArray alloc] init];
for(NSString *key in self.keys)
{
NSMutableArray *array = [names valueForKey:key];
NSMutableArray *toRemove = [[NSMutableArray alloc] init];
for(NSString *name in array)
{
if([name rangeOfString:searchTerm
options:NSCaseInsensitiveSearch].location == NSNotFound)
[toRemove addObject:name];
}
if([array count] == [toRemove count])
[sectionsToRemove addObject:key];
[array removeObjectsInArray:toRemove];
[toRemove release];
}
[self.keys removeObjectsInArray:sectionsToRemove];
[sectionsToRemove release];
[table reloadData];
}
I am assigning array from the result of this [names valueForKey:key];
array is of type 'MutableArray' What am I missing?
Thanks!
valueForKey: returns an NSArray. It doesn't matter that you are sending it to an NSMutableArray.
You could either cast the result to (NSMutableArray *), but my personal preference is to get a copy:
NSMutableArray *array = [[[names valueForKey:key] mutableCopy] autorelease];
The variable is statically typed as an NSMutableArray, but it would appear that the object assigned to the variable is not an NSMutableArray. The type of the variable is just a hint for the compiler to use when type checking and choosing method signatures — you still have to take some care to make sure you're actually assigning the type that the variable says it should hold.
I am doing something wrong here... I know that
I'm using Xcode and I have created the following class using the data modeller:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface Project : NSManagedObject {
#private
}
#property (nonatomic, retain) NSNumber * indent;
#property (nonatomic, retain) NSNumber * collapsed;
#property (nonatomic, retain) NSString * color;
#property (nonatomic, retain) NSNumber * project_id;
#property (nonatomic, retain) NSNumber * item_order;
#property (nonatomic, retain) NSNumber * cache_count;
#property (nonatomic, retain) NSNumber * user_id;
#property (nonatomic, retain) NSString * name;
#end
When I am trying to propagate this class with data from a JSON source using the following code:
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"projects" ofType:#"json"];
if (filePath) {
NSString* jsonString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
DLog(#"JSON for Projects:%#", jsonString);
SBJsonParser* jsonParser = [SBJsonParser new];
id response = [jsonParser objectWithString:jsonString];
NSArray* array = (NSArray*) response;
NSEnumerator* e = [array objectEnumerator];
NSDictionary* dictionary;
while ((dictionary = (NSDictionary*)[e nextObject])) {
Project* project = [[Project alloc] init];
project.user_id = [dictionary objectForKey:#"user_id"];
project.name = [dictionary objectForKey:#"name"];
project.color = [dictionary objectForKey:#"color"];
project.collapsed = [dictionary objectForKey:#"collapsed"];
project.item_order = [dictionary objectForKey:#"item_order"];
project.cache_count = [dictionary objectForKey:#"cache_count"];
project.indent = [dictionary objectForKey:#"indent"];
project.project_id = [dictionary objectForKey:#"project_id"];
[elementArray addObject:project];
[project release];
}
}
However, the code stops at the project.user_id = [dictionary objectForKey:#"user_id"]; line with an exception "* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Project setUser_id:]: unrecognized selector sent to instance 0x590bcb0'"
I don't know why this is happening or how to resolve this.
I've set up a reality distortion field so I don't violate my NDA. And now I can answer your question, it has nothing to do with the product-that-must-not-be-named anyway.
There is your bug: Project* project = [[Project alloc] init];
The #dynamic setters and getters are not created for you if you create your object this way.
You can't use NSManagedObjects without a NSManagedObjectContext.
You should use something like this:
Project *project = (Project *)[NSEntityDescription insertNewObjectForEntityForName:#"Project" inManagedObjectContext:self.managedObjectContext];
Property names with underscores are not very sensible in the Objective C world - I guess the properties generated by Core Data have the wrong names therefore. Try using CamelCase, that is calling your properties userID, itemOrder, cacheCount etc.
You may need to set up your getters and setters.
It could be as simple as adding:
#synthesize user_id;
In your class file.