Core Data: storing web content before saving - iphone

I am currently developing an app that relies heavily on retrieving web content. I have a series of NSObject classes that represent the content that I retrieve in JSON format. I also have NSManagedObject classes that represent my Core Data model that are almost identical.
Here is an example of an NSObject class that I use to hold my web content:
#interface MovieRecord : NSObject {
NSString *movieTitle;
NSDecimalNumber *movieId;
NSString *movieRating;
NSString *movieDescription;
NSDate *movieReleaseDate;
NSMutableArray *movieVideos; // collection of class videoRecord
NSMutableArray *actors;
UIImage *movieImage;
NSURL *movieImageURL;
}
And here is an example of my NSManagedObject class:
#interface Movie : NSManagedObject
{
}
#property (nonatomic, retain) NSNumber * id;
#property (nonatomic, retain) NSString * description;
#property (nonatomic, retain) id image;
#property (nonatomic, retain) NSString * rating;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSDate * releaseDate;
#property (nonatomic, retain) NSString * imageURL;
#property (nonatomic, retain) NSSet* actors;
#end
In this example a user will look through a lot of movies but they will not always be saving the movie to the persistent store. That was my main reason for storing the information into seperate classes first, then if they decide to save it, I will populate the NSManaged object classes and save. The NSObject class will not be fully populated until a user has drilled down to the detail view (initially only movieTitle and movieID will be set).
I guess my question here is does it makes sense to keep these classes separate? Is there a better design approach to this that I am just not seeing? Should I just stick to using the NSDictionary to populate my table views (NSDictionary is populated from the JSON data)?

I am not sure if you got to the bottom of this but I'd suggest you use a NSManagedObject for all your objects and only persist the ones you're interested in.
Remember all your objects are kept in the context and not persisted until you explicitly save the context. Before that you can get rid of unwanted stuff and then sent the context a save message.

You don't have to insert managed objects into a context, as long as you initialize them with the correct information from your managed object model. Assuming that you can get your managed object model from the app delegate, you can do something like the following:
NSManagedObjectModel *objectModel = [appDelegate managedObjectModel];
Movie *obj = [[Movie alloc]
initWithEntity:[[objectModel entitiesByName] objectForKey:#"Movie"]
insertIntoManagedObjectContext:nil
];

Related

How to filter an array using an array?

I have three types of custom classes (see below) and three arrays componentList, componentGroupList and componentGroupItemList. Arrays are not linked, each of them contain all objects. I need to filter specific component, all its related groups and all their related items.
Now, I know how to filter componentList using #"componentId==123" and get desired component object. I can also filter its groups from componentGroupList using the same predicate, because ComponentGroup object contains the same componentId key. However, I don't know how to filter related ComponentGroupItem objects from componentGroupItemList.
Currently, I have filtered array containing ComponentGroup objects, and I would like to filter componentGroupItemList using that array. Is it possible, or do I need to extract all "groupId" values from filteredComponentGroupList into a string and then make some predicate?
The classes:
#interface Component : NSObject
#property (nonatomic, strong) NSNumber *componentId;
#property (nonatomic, strong) NSString *title;
#end
#interface ComponentGroup : NSObject
#property (nonatomic, strong) NSNumber *groupId;
#property (nonatomic, strong) NSNumber *componentId;
#property (nonatomic, strong) NSString *title;
#end
#interface ComponentGroupItem : NSObject
#property (nonatomic, strong) NSNumber *itemId;
#property (nonatomic, strong) NSNumber *groupId;
#property (nonatomic, strong) NSString *title;
#end
You have to extract the group ids first
NSArray *groupIds = [filteredComponentGroupList valueForKey:#"groupId"];
and use that for a predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"groupId IN %#", groupIds];
NSArray *filteredComponentGroupItemList = [componentGroupItemList filteredArrayUsingPredicate:predicate];
At a first glance your data structure seems a bit redundant, but I guess you have thought it through.
If I understand your requirements correctly, you have an array of component groups already filtered (let's call it filteredComponentGroups) and you wish to filter another array (componentGroupItemList) using filteredComponentGroups.
In that case you can use the IN operator of NSPredicate and construct the array of IDs with valueForKey: on the array. valueForKey: on an array constructs a new array with only the values of that key of each object in the original collection. Very powerful for situations like this one.
NSArray *filteredComponentGroups = // ... your filtered components
NSArray *componentIdsFromFilteredComponentGroups = [filteredComponentGroups valueForKey: #"groupId"];
NSPredicate *inFilteredComponentGroupsP = [NSPredicate predicateWithFormat: #"groupId IN %#", componentIdsFromFilteredComponentGroups];
NSArray *filteredGroupItemList = [componentGroupItemList filteredArrayUsingPredicate: inFilteredComponentGroupsP];
Typed directly in the browser, so beware of typos.

NSManagedObject exception “this class is not key value coding-compliant” and crashes the app in ios 6 but works for ios 5

I having an issue where my application crashes with the following exception:
ABC[1936:c07] * Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<_NSObjectID_48_0 0xb63e310> valueForUndefinedKey:]: this class is not key value coding-compliant for the key id.'
The strange issue with this exception is that it does not occur when using iOS5. Please see the code where the exception takes place below:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if ((self.sectionInfoArray == nil) ||
([self.sectionInfoArray count] != [self numberOfSectionsInTableView:self.tableView]))
{
NSMutableArray *infoArray = [[NSMutableArray alloc] init];
for (Tour *tour in self.tours)
{
SectionInfo *sectionInfo = [[SectionInfo alloc] init];
sectionInfo.tour = tour;
sectionInfo.open = NO;
NSLog(#"Tour Details Count %#", [[tour tourDetails] objectAtIndex:0]);
NSNumber *defaultRowHeight = [NSNumber numberWithInteger:DEFAULT_ROW_HEIGHT];
NSInteger countOfQuotations = [[sectionInfo.tour tourDetails] count];
for (NSInteger i = 0; i < countOfQuotations; i++)
{
[sectionInfo insertObject:defaultRowHeight inRowHeightsAtIndex:i];
}
[infoArray addObject:sectionInfo];
}
self.sectionInfoArray = infoArray;
}
}
Would this exception be being caused because due to me having a Fetched Property defined within the class Tour that gets an array of TourDetail classes. Please see the implementation code for both classes below:
#import "Tour.h"
#import "TourDetail.h"
#implementation Tour
#dynamic background_url;
#dynamic id;
#dynamic summary;
#dynamic title;
#dynamic tour_tourdetail;
#dynamic tourDetails;
#end
#import "TourDetail.h"
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class TourDetail;
#interface Tour : NSManagedObject
#property (nonatomic, retain) NSString * background_url;
#property (nonatomic, retain) NSNumber * id;
#property (nonatomic, retain) NSString * summary;
#property (nonatomic, retain) NSString * title;
#property (nonatomic, retain) TourDetail *tour_tourdetail;
#property (nonatomic, retain) NSArray *tourDetails;
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface TourDetail : NSManagedObject
#property (nonatomic, retain) NSString * audiofile;
#property (nonatomic, retain) NSString * detail;
#property (nonatomic, retain) NSNumber * id;
#property (nonatomic, retain) NSNumber * lattitude;
#property (nonatomic, retain) NSNumber * longitude;
#property (nonatomic, retain) NSString * title;
#property (nonatomic, retain) NSNumber * tour_id;
#property (nonatomic, retain) NSManagedObject *tourdetail_tour;
#end
#implementation TourDetail
#dynamic audiofile;
#dynamic detail;
#dynamic id;
#dynamic lattitude;
#dynamic longitude;
#dynamic title;
#dynamic tour_id;
#dynamic tourdetail_tour;
#end
Any help with this issue would be greatly appreciated. As I am at a loss as to how I can fix this.
Thanks,
Michael
UPDATE:
When I remove the Fetched Property the exception does not occur with iOS6. Please see the predicate I have configured below:
Fetched Property tourDetails Predicate tour_id == $FETCH_SOURCE.id
Can you see anything I doing wrong with the setup of this predicate ? My goal is to use this so as I can return an Array of TourDetail objects for each tour_id that mathces the id column inside the Tour table.
UPDATE:
I have been able to diagnose that the exception is being thrown because of the Predicate as when I call both tables separately there is no exception raised. Can you see any issue with the predicate that I have created ?
Please see code below showing how I am retrieving the objects from the Core Data DB:
- (void)viewDidLoad
{
[super viewDidLoad];
[DrivingToursContent setupStaticData];
self.tableView.sectionHeaderHeight = HEADER_HEIGHT;
_openSectionIndex = NSNotFound;
self.tableView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"custombackground.ptoung"]];
self.managedObjectContext = [[BaseCoreDataController sharedInstance] newManagedObjectContext];
[self loadRecordsFromCoreData];
[self loadRecordsFromCoreDataForTourDetail];
NSLog(#"Tour Detail array count: %d", [self.toursTest count]);
// Do any additional setup after loading the view.
}
- (void)loadRecordsFromCoreData {
[self.managedObjectContext performBlockAndWait:^{
[self.managedObjectContext reset];
NSError *error = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([Tour class])];
[request setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"id" ascending:YES]]];
self.tours = [self.managedObjectContext executeFetchRequest:request error:&error];
}];
}
UPDATE:
The root of the problem is definitely coming from the Predicate I have defined for the Fetched property but can you advise how I should write the predicate to link between the 2 tables. As when I write the predicate tour_id == 0 and directly reference an id I know exists the fetched property works correctly. But when I use $FETCH_SOURCE.id the key value coding exception is thrown. What property do you use to reference the the table you wish to link to ?
Really appreciate all your help with this.
Thanks,
Michael
without seeing your code, you are calling a method on a class that was deprecated in iOS6, hence the crash.
There's nothing in the code you've posted for viewWillAppear that touches Core Data in any obvious way at all, which makes it kind of hard to guess what's going on. Some of those objects might be managed objects, but who knows which ones or how you created them?
However the error message does provide a huge clue:
[<_NSObjectID_48_0 0xb63e310> valueForUndefinedKey:]: this class is not key value coding-compliant for the key id.
The fact that this mentions _NSObjectID_48_0 indicates that at some point you're using NSManagedObjectID instances when you're expecting NSManagedObject instances. NSManagedObjectID does not have a property named id, no matter what the entity looks like, so asking one for its id will produce a "not key value coding compliant" error.
As for why this would be happening, it's still impossible to say, since you haven't posted any code that gives any clues about how you are using Core Data.
It might be something to do with the predicate:
tour_id == $FETCH_SOURCE.id
If you constructed a fetch so that $FETCH_SOURCE is an NSManagedObjectID, this would be a problem. Maybe (and I can only guess) you're using it while asking for a result type of NSManagedObjectIDResultType.
#property (nonatomic, retain) NSNumber * id;
Isn't id a reserved word in Objective-C? Have you tried renaming this property to something else, to see if that is causing any conflicts?
Without digging into your code, NSFetchRequest can set the returned result type,
e.g., NSManagedObjectIDResultType or NSManagedObjectResultType.
NSManagedObjectResultType is wanted, but NSManagedObjectIDResultType is returned.
I have the same issue when I use MagicalRecord, but have no issue when use Xcode generate CoreData source code.

Serializing a nested object when posting from iOS to Rails app

Hoping to get a little push in the right direction. I am having trouble getting a nested object to serialize properly when I POST to my rails app using RestKit. I have the following mappings:
RKObjectMapping *cartSerializationMapping = [RKObjectMapping mappingForClass:[TOCart class]];
[cartSerializationMapping mapKeyPath:#"place.placeID" toAttribute:#"order[external_id]"];
//map the line items serialization mapping
RKObjectMapping *lineItemSerializationMapping = [RKObjectMapping mappingForClass:[TOLineItem class]];
[lineItemSerializationMapping mapKeyPath:#"itemID" toAttribute:#"itemID"];
[lineItemSerializationMapping mapKeyPath:#"name" toAttribute:#"name"];
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:lineItemSerializationMapping forClass:[TOLineItem class]];
//add relationship bw line items to TOLineItem
[cartSerializationMapping mapKeyPath:#"line_items" toRelationship:#"order[line_items]" withMapping:lineItemSerializationMapping serialize:YES];
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:cartSerializationMapping forClass:[TOCart class]];
After posting to the server, serialization works for the parent object but not for the nested line_item object:
Started POST "/orders" for 127.0.0.1 at 2011-11-16 04:05:58 -0800
Processing by OrdersController#create as JSON
Parameters: {"order"=>{"line_items"=>["<TOLineItem: 0x8aafdb0>"], "external_id"=>"4ae8a535f964a52024b121e3"}}
I want the line_item to serialize to itemID and name etc...
Did I set my mappings incorrectly?
Thanks!
UPDATE:
My TOCart class:
#import <Foundation/Foundation.h>
#class TOPlace;
#interface TOCart : NSObject
{
NSNumber *cartID;
TOPlace *place; //post to external id
NSString *state;
NSMutableArray *line_items;
}
#property (nonatomic, retain) NSNumber *cartID;
#property (nonatomic, retain) TOPlace *place;
#property (nonatomic, retain) NSString *state;
#property (nonatomic, retain) NSMutableArray *line_items;
#end
I always define my mapping to map from API to entities & then create the serialization mapping with [myMappingFromApi inverseMapping] selector. You can find further details in my answer to somewhat different question, but definitely related: RestKit: How does one post an array of objects?.

CoreData, JSON "unrecognized selector sent to instance"

I am trying to parse some JSON for my app.
When trying to set the "name" value I get the error unrecognized selector sent to instance'.
This is the line of code the error happens on: self.name = [json_object objectForKey:#"name"]; (json_object is an NSDictionary and "self" is an object in CoreData)
My JSON looks like this:
json_object: {
draftstatus = "isdraftfull.php";
getcard = "getcard.php";
getpack = "getpack.php";
imageextension = ".jpg";
images = "images/small/";
joindraft = "joindraft.php";
joindraftmenu = "getjoindraftmenu.php";
login = "loginasuser.php";
name = "the server";
passpack = "passpack.php";
playerdraftstatus = "getplayerdraftstatus.php";
signup = "signup.php";
userexists = "checkifuserexists.php";
usesetidforimagepath = false;
}
Server.h looks like this:
#import <CoreData/CoreData.h>
#import "JSON.h"
#interface Server : NSManagedObject
{
NSMutableData *responseData;
}
#property (nonatomic, retain) NSString * playerdraftstatus;
#property (nonatomic, retain) NSString * signup;
#property (nonatomic, retain) NSNumber * usesetidforimagepath;
#property (nonatomic, retain) NSString * login;
#property (nonatomic, retain) NSString * imageextension;
#property (nonatomic, retain) NSString * userexists;
#property (nonatomic, retain) NSString * getcard;
#property (nonatomic, retain) NSString * passpack;
#property (nonatomic, retain) NSString * draftstatus;
#property (nonatomic, retain) NSString * joindraftmenu;
#property (nonatomic, retain) NSString * getpack;
#property (nonatomic, retain) NSString * joindraft;
#property (nonatomic, retain) NSString * images;
#property (nonatomic, retain) NSNumber * isdefaultserver;
#property (nonatomic, retain) NSString * root;
#property (nonatomic, retain) NSString * name;
- (id) initWithWebAddress:(NSString *) address;
- (id) initWithWebAddressAsyn:(NSString *) address;
- (void) fillFromJSON:(NSDictionary *) json_object;
- (void) saveServer;
#end
I believe it was working a few days ago, but I don't remember. When I started working on it today I added the "root" object. It compiles with no errors but won't parse that json.
Help?
EDIT
I changed the JSON to come back with name = "the server" and when I log that like this NSLog(#"json_object name: %#", [json_object objectForKey:#"name"]); the log says json_object name: the server but I still get the same error when I try to set that to self.name which is of type String.
I did notice that sometimes when examining the Log version of the JSON that the value inside name isn't always surrounded in quotes and as you see from the log of [json_object objectForKey:#"name"] it is showing without quotes around it. Is that the problem and why is that happening? If I look at the json straight on the web page that outputs it, there is ALWAYS quotes around the value.
EDIT 2
The full error is as follows:
-[Server setName:]: unrecognized selector sent to instance 0x61036e0
2010-11-19 15:46:10.113 TCGDraft[57953:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Server setName:]: unrecognized selector sent to instance 0x61036e0'
*** Call stack at first throw:
EDIT 3
I did an NSLog on "self" and this is what comes back self: <Server: 0x6143190> (entity (null); id: (null) ; data: {})
I have no clue if that is what is normal though. This is a CoreData object and I have a feeling I might have messed it up somehow.
EDIT 4
I went back and looked at the code to create a Server Object from CoreData like this:
Server *server = [NSEntityDescription
insertNewObjectForEntityForName:#"Server"
inManagedObjectContext:context];
When I ran that I now get an error that just says EXC_BAD_ACCESS so it does look like somehow that object is messed up...but I don't know how.
EDIT 5
Right, so I completely deleted the Server.h and Server.m files with my custom code, I recreated them from the CoreData object and tried that code I posted in "Edit 4" and I STILL get the same error about EXC_BAD_ACCESS....so now I have apparently gone BACKWARD rather than Forward.
Firstly, I suggest you separate the NSManagedObject from your JSON parser methods completely. All the async loading and processing of your JSON data should be dealt with in a separate object that you can then assign to your managed object properties.
#import <CoreData/CoreData.h>
#interface Server : NSManagedObject
{
}
#property (nonatomic, retain) NSString * playerdraftstatus;
#property (nonatomic, retain) NSString * signup;
...
#property (nonatomic, retain) NSString * name;
#end
Secondly, DO NOT change the #dynamic values back to #synthesize otherwise CoreData will not be able to manipulate and save these objects for you (that's most likely why you are getting the EXEC_BAD_ACCESS exception when you try and save the object!).
#dynamic *playerdraftstatus;
#dynamic *signup;
...
#dynamic *name;
Now try assigning the attribute values from the controller where you are creating your Server object i.e.
Server *server = [NSEntityDescription
insertNewObjectForEntityForName:#"Server"
inManagedObjectContext:context];
server.name = [json_object valueForKey:#"name"];
And finally, make sure you don't ever release the server object as CoreData will look after this for you automatically.
PS: if you changed your data model you may have to reset/delete the app from the iPhone/Simulator otherwise you will get an error message saying the store type is incompatible with the existing one on the phone etc.
Let me know how you go and if you get any further error messages.
Cheers,
Rog
The problem is that you don't have a setName method in your Server class. It has nothing to do with the json data. Make sure you synthesize your properties, or otherwise define the requisite methods.

Accessing NSString properties of a Core Data entity

I'm moving my initial steps in the Core Data realm (and I'm quite new to iPhone development, too) and I found a behavior I cannot explain.
I declared a subclass of a NSManagedObject and defined a few properties, some of them of type NSString *, MyObject.h is something like:
#interface MyObject : NSManagedObject {
}
#property (nonatomic, retain) NSString *contentFile;
#property (nonatomic, retain) NSString *contentPath;
#property (nonatomic, retain) NSDate *creationDate;
#property (nonatomic, retain) NSString *name;
#end
Now, if I try to create an object instance and assign a value to the name property, when I try to print back the content of the property it seems it's mangled.
In the AppDelegate, where the whole Core Data stack is defined, I write:
MyObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:#"MyObject"
inManagedObjectContext:self.managedObjectContext];
newObject.name = #"Testing";
NSLog([NSString stringWithFormat:#"Name: %s\n", newObject.name]);
What I get in the output console is
2009-08-24 20:03:55.176 MyApp[15727:20b] Name: ‡}00»
I can't understand if I'm doing something wrong or if I forgot something. Anyone can help, please?
You need to use the %# format specifier in stringWithFormat:, since NSString is an Objective-C object:
NSLog([NSString stringWithFormat:#"Name: %#\n", newObject.name]);
%s is used for C-strings (char*s). For more info look at String Format Specifiers.
The correct format specifier for Objective-C objects is %#, not %s.
You should use %#, not %s. %s is for char* strings. You're passing in an objective-c object.