Objective-C: Many to many relationship with CoreData - iphone

I've an iPhone applications with 2 models, Category and Content, which have a many-to-many relationship.
This is the code:
Content
#interface Content : NSManagedObject {
}
#property(readwrite, retain) NSString *type;
#property(readwrite, retain) NSString *mainText;
...
#property (copy) NSSet * categories;
#end
Category
#interface Category : NSManagedObject {
}
#property (nonatomic, retain) NSNumber * id;
#property (nonatomic, retain) NSNumber * active;
...
#property (copy) NSSet * contents;
#end
And then this operation:
...
NSSet *tmp_set = [NSSet setWithArray:some_array_with_contents objectsAtIndexes:custom_indexes]];
cat.contents = tmp_set;
[[DataModel managedObjectContext] save:&error];
...
On the last line, the app crashes badly saying:
-[__NSCFSet _isValidRelationshipDestination__]: unrecognized selector sent to instance 0x5c3bbc0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet _isValidRelationshipDestination__]: unrecognized selector sent to instance 0x5c3bbc0'

Your relationship properties should not use copy. They should retain e.g:
#property (nonatomic, retain) NSSet* categories;
You don't want to copy a set of managed objects because you would end up with duplicate objects in the object graph. That is going to cause big problem.
However, that is not the immediate problem. The immediate problem is that something is causing a selector intended for a managed object to be sent to the set itself.
Most likely this is caused by directly assigning the copied set to the relationship directly instead of using one of the accessor methods defined in the .m file. The #dynamic directive will not create a setCategories method because this is a managed object so you don't get proper KVO notifications and the context does not update properly. When it tries to save it sends validation messages to the set object instead of the objects it contains.
You should have a method like addCategoryObjects: in the implementation file. Removing the copy and using those methods should resolve the problem.

Related

Can I use a custom initializer for a core data model object?

I use Core Data and have an object ExerciseForRoutine. I'm currently manually creating it and then settings it's attributes, which seems to waste code. Is there any way I can create a custom init method to handle this in one line (I know how to do around alloc/init, but core data has a different init method..)
Current Code:
ExerciseForRoutine *exerciseForRoutine = (ExerciseForRoutine *)[NSEntityDescription insertNewObjectForEntityForName:#"ExerciseForRoutine" inManagedObjectContext:managedObjectContext];
exerciseForRoutine.name = self.selectedExercise;
exerciseForRoutine.timeStamp = date;
exerciseForRoutine.muscleGroup = self.muscleName;
exerciseForRoutine.musclePicture = self.muscleURL;
ExerciseForRoutine Class
#class Routine;
#interface ExerciseForRoutine : NSManagedObject {
#private
}
#property (nonatomic, strong) NSDate * timeStamp;
#property (nonatomic, strong) NSString * name;
#property (nonatomic, strong) NSString * muscleGroup;
#property (nonatomic, strong) NSString * musclePicture;
#property (nonatomic, strong) Routine * exerciseToRoutine;
#end
#implementation ExerciseForRoutine
#dynamic timeStamp;
#dynamic name;
#dynamic muscleGroup;
#dynamic musclePicture;
#dynamic exerciseToRoutine;
I did this using awakeFromInsert and awakeFromFetch.
From Apple's documentation:
In a typical Cocoa class, you usually override the designated initializer (often the init method). In a subclass of NSManagedObject, there are three different ways you can customize initialization —by overriding initWithEntity:insertIntoManagedObjectContext:, awakeFromInsert, or awakeFromFetch. You should not override init. You are discouraged from overriding initWithEntity:insertIntoManagedObjectContext: as state changes made in this method may not be properly integrated with undo and redo.
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdManagedObjects.html
The classes which Xcode creates for handling core data objects should not be overridden, instead what you could do is create your own custom class which inherits from NSObject and write your methods to handle the managed object their.
Sol: You can do this with the help of the parameterized init method
Then it would look something like this
CoreDataHelperClass *someobj = [[CoreDataHelperClass alloc]initWithname:#"name" andTimeStamp:#"Time" andMuscleGroup:#"musclegroup" andPicture:UIImagePNGRepresentation(someimageObj)];
To do the above you need to add your own init method in the CoreDataHelperClass class like this
.h part of CoreDataHelperClass
- (id)initWithName:(NSString*)name andTimeStamp:(NSString*)timeStamp andMuscleGroup:(NSString*)group andPicture:(NSData*)imageData;
.m part of CoreDataHelperClass
- (id)initWithName:(NSString*)name andTimeStamp:(NSString*)timeStamp andMuscleGroup:(NSString*)group andPicture:(NSData*)imageData
{
//you assignment code to the core data attributes goes here
ExerciseForRoutine *obj = [[ExerciseForRoutine alloc]init];
obj.name = name;
obj.timestamp = timeStamp;
//and so on
return self;
}
Anyways what you could also do is pass a dictionary with the keyvalue pair get the values in your custom class or you may also pass an NSMutableArray like what ever suits your business model both will work.
You can get the values of Dictionary or Array inside your CoreDataHelperClass and assign those values to your attribute.
Hope i have got your query right if not then kindly mention the error part via comments
To add to #Radix's answer, you should consider using mogenerator because it'll do much of that subclassing business for you.
http://rentzsch.github.io/mogenerator/
See here for a guide to set it up and have it running on XCode 5.
There's a small caveat to watch out for though: if you get an assertion failure that reads:
-[MOGeneratorApp setModel:] blah blah blah
Then you should point mogenerator to the .xcdatamodel file inside of the .xcdatamodeld package in your Run Script Phase, like so:
mogenerator -m Model.xcdatamodeld/Model.xcdatamodel -O Project/Model --template-var arc=true
Where Project is the name of your project and Model is the name of your model.
See https://github.com/rentzsch/mogenerator/issues/169.

error when using core data

hello i'm trying to use core data to read and write user data.
the code i'm using is as follows:
AppDelegate interface
#interface PopAdsAppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>
{
#private
NSManagedObjectContext *managedObjectContext_;
NSManagedObjectModel *managedObjectModel_;
NSPersistentStoreCoordinator *persistentStoreCoordinator_;
}
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) UITabBarController *tabBarController;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
#end
the code i'm getting error in is this in the .m file:
PopAdsAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
this line in specific
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
the .xcdatamodeld file i have is called UserData.xcdatamodeld and i have entity called "User".
To be honest i don;t know where in the code i should provide the UserData.xcdatamodeld file name?!! all i see in the examples is only the entity name!
the error i'm getting is:
[PopAdsAppDelegate managedObjectContext]: unrecognized selector sent to instance 0x180d60
2011-12-25 13:36:37.008 PopAds2[15645:707] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[PopAdsAppDelegate managedObjectContext]: unrecognized selector sent to instance 0x180d60'
* First throw call stack:
(0x3435f8bf 0x345af1e5 0x34362acb 0x34361945 0x342bc680 0x3c87 0x41a9 0x35d6bc39 0x35cc36e9 0x35cc36b3 0x35cc35d5 0x323718a5 0x32366545 0x32366639 0x32366243 0x32366179 0x34333b03 0x343332cf 0x34332075 0x342b54dd 0x342b53a5 0x30b39fcd 0x37736743 0x305d 0x2ff4)
terminate called throwing an exceptionkill
Your appDelegate have no managedObjectContext method as it seems from the error.
Edit:
Take a look at those links:
Ray Wenderlich Blog
Cocoa Dev Central
Mobile Tuts
And if those aren't enought, you could always Google it :)
Why don't you create a new project in Xcode and select the "Core Data" checkbox in the wizard? Xcode will then create a new project for you that uses Core Data. You usually don't have to setup Core Data yourself.
It seems that you have not synthesized your property
Add
#synthesize managedObjectContext;
to your AppDelegate.m file
EDIT
I thought you just forgot to initialize your property but it seems that you are just beginning to learn CoreData.
The more efficient approach to learn how to initialize CoreData is to study the code of the defaut implementation provided by Apple. Create a new project and in the wizard chose to use CoreData. You will get a working code and see the two setters being overridden. At their first call the private attributes are initialized. This is lazy loading, initialize when first need it.
You should also read the CoreData starting point and especially CoreData programming guide

Release of NSManagedObject

I have based a portion of an app on Apple's CoreDataRecipes example code attainable at
http://developer.apple.com/library/ios/#samplecode/iPhoneCoreDataRecipes/Introduction/Intro.html
After some modifications I spent a good few hours tracking down a bug which I must have introduced, but which I solved by removing two lines of code present in apple's code.
I added an author attribute to the NSManagedDataObject recipe, identical in implementation - as far as I could tell - to other string attributes which recipe already had. My new attribute became a zombie after entering and leaving the modal view controlled by IngredientDetailViewController. The dealloc method of IngredientDetailViewController was
- (void)dealloc {
[recipe release];
[ingredient release];
[super dealloc];
}
Having tracked down the bug, I commented out the releases on the recipe and the ingredient (another NSManagedObject) and my app now seems to be functioning. I have now discovered that my code works with or without those release calls; the bug must have been fixed by another change I made. I am now wondering
Why was apple's example code written like this originally?
What was it about the original attributes of the NSManagedObject recipe which meant that they were not susceptible to zombification from the dealloc calls?
If the above hasn't displayed my ignorance enough, I should point out that I am new to Objective C and iPhone development but I would really like to understand what's going on here.
EDITED IN RESPONSE TO COMMENTS AND UPDATED:
I now cannot replicate the zombie creation by uncommenting those lines, obviously another change during bugshooting did the trick. Some of what I originally asked is now invalid but this has left me further confused as to the use of release for NSManagedObjects, since now functionality seems identical with or without those calls. My main question now is just whether or not they should be there. The crash was occuring upon saving in the IngredientDetailView. Here is the header:
#class Recipe, Ingredient, EditingTableViewCell;
#interface IngredientDetailViewController : UITableViewController {
#private
Recipe *recipe;
Ingredient *ingredient;
EditingTableViewCell *editingTableViewCell;
}
#property (nonatomic, retain) Recipe *recipe;
#property (nonatomic, retain) Ingredient *ingredient;
#property (nonatomic, assign) IBOutlet EditingTableViewCell *editingTableViewCell;
#end
and the save method:
- (void)save:(id)sender {
NSManagedObjectContext *context = [recipe managedObjectContext];
/*
If there isn't an ingredient object, create and configure one.
*/
if (!ingredient) {
self.ingredient = [NSEntityDescription insertNewObjectForEntityForName:#"Ingredient"
inManagedObjectContext:context];
[recipe addIngredientsObject:ingredient];
ingredient.displayOrder = [NSNumber numberWithInteger:[recipe.ingredients count]];
}
/*
Update the ingredient from the values in the text fields.
*/
EditingTableViewCell *cell;
cell = (EditingTableViewCell *)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
ingredient.name = cell.textField.text;
cell = (EditingTableViewCell *)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0]];
ingredient.amount = cell.textField.text;
/*
Save the managed object context.
*/
NSError *error = nil;
if (![context save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate.
You should not use this function in a shipping application, although it may be useful during development.
If it is not possible to recover from the error, display an alert panel that instructs the user to quit the
application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self.navigationController popViewControllerAnimated:YES];
NSLog(#"in ingredient detail save after ingredient pop; - recipe.author is %#", recipe.author);
}
since I'm a new user I can't put the screenshot of the data model here, so here is a link to it: data model screenshot
and finally the Recipe header:
#interface ImageToDataTransformer : NSValueTransformer {
}
#end
#interface Recipe : NSManagedObject {
}
#property (nonatomic, retain) NSString *instructions;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSString *overview;
#property (nonatomic, retain) NSString *prepTime;
#property (nonatomic, retain) NSSet *ingredients;
#property (nonatomic, retain) UIImage *thumbnailImage;
#property (nonatomic, retain) NSString *author;
#property (nonatomic) BOOL *isDownloaded;
#property (nonatomic) BOOL *isSubmitted;
#property (nonatomic, retain) NSString *uniqueID;
#property (nonatomic) float averageRating;
#property (nonatomic) float numberOfRatings;
#property (nonatomic, retain) NSManagedObject *image;
#property (nonatomic, retain) NSManagedObject *type;
#end
#interface Recipe (CoreDataGeneratedAccessors)
- (void)addIngredientsObject:(NSManagedObject *)value;
- (void)removeIngredientsObject:(NSManagedObject *)value;
- (void)addIngredients:(NSSet *)value;
- (void)removeIngredients:(NSSet *)value;
#end
Thanks again.
Please take a look at Core Data documentation, since Core Data “owns” the life-cycle of managed objects you should not be releasing them at all.
The only time you would release a managed object would be if you had retained it yourself. Seeing as your property definition says that it is retaining the recipe and ingredient objects, when your ingredientviewcontroller is deallocated, it needs to release the recipe and ingredient objects.
When you do something like myIngredientViewController.ingredient = anIngredient, it's like calling a method which would look something like:
- (void)setIngredient:(Ingredient *)ing {
[self willChangeValueForKey:#"ingredient"];
Ingredient *oldIngredient = ingredient;
ingredient = [ing retain];
[oldIngredient release];
[self didChangeValueForKey:#"ingredient"];
}
So in your save method, when it assigns self.ingredient = ..., that is retaining your object yourself - you now have an ownership interest in that object, so you need to release that in your dealloc.
If you think about it in another way, the managed object context has added 1 to the retain count because it has an ownership interest in it, and you have added 1 to the retain count because you want to maintain an ownership interest in it. When you relinquish your ownership interest, by releasing it during dealloc, the retain count goes down 1 and when the managed object context releases it, the retain count would go to zero and it would be deallocated.
That is how normal objects operate, and how you would treat managed objects in most circumstances, but there are a few caveats for managed objects - as the previous poster indicated, the lifecycle of managed objects is controlled by the managed object context, and there are various things that can happen to a managed object that may mean that although the object still exists, it may be deleted in the context, or a fault in the context, or maybe even reused with different data.
You don't usually have to worry about that, but if you use custom managed objects which have their own instance variables that you need to manage the memory for, or other things you want to do when they are created, fetched, turned into faults etc, then you would need to look at the awakeFromInsert, awakeFromFetch, willTurnIntoFault, didTurnIntoFault etc.
But all that is advanced stuff that you won't need until you get into more complex scenarios.
HTH

Crash when manipulating a simple Core Data object

I'm diving into iOS development and I have a few questions about manipulating a simple Core Data object that I created in Xcode. After using the object editor, here's the class that Xcode generated for me...
#interface Alarm : NSManagedObject
{
}
#property (nonatomic, retain) NSNumber * Enabled;
#property (nonatomic, retain) NSString * Label;
#property (nonatomic, retain) NSNumber * Snooze;
#end
#implementation Alarm
#dynamic Enabled;
#dynamic Label;
#dynamic Snooze;
#end
Here's a code snipped where I try and create an Alarm object that I plan to add to my ManagedObjectContext...
- (void)saveAlarm:(id)sender {
Alarm *alarm = [[Alarm alloc] init];
alarm.Label = [NSString stringWithString:txtLabel.text];
alarm.Snooze = [NSNumber numberWithBool:switchSnooze.on];
alarm.Enabled = [NSNumber numberWithBool:YES];
[addAlarmDelegate insertNewAlarm:alarm];
[alarm release];
}
My code crashes the first time I try and assign a value to one of the properties of alarm, on the line...
alarm.Label = [NSString stringWithString:txtLabel.text];
with the following crash message in the console...
reason: '-[Alarm setLabel:]: unrecognized selector sent to instance 0x5e33640
what am I missing here?
Thanks so much in advance for your help!
I would look into using mogenerator:
http://rentzsch.github.com/mogenerator/
The command line to run it is:
mogenerator -m MyAwesomeApp.xcdatamodel -O Classes
Whatever directory you put after -O is where the produced classes go. The great thing is it has simpler methods to create new manage objects in a context, and also produces a class you can customize (adding your own methods) that do not get removed even when you regenerate objects from your model.
Much simpler than using the XCode object generation.
You should not allocate and init an NSManagedObject-based object directly. You should use
[NSEntityDescription insertNewObjectForEntityForName:#"Alarm" inManagedObjectContext:moc];
It might be the reason for it not to work. Because it is usually pretty straight forward to make it work.
The documentation says:
If you instantiate a managed object
directly, you must call the designated
initializer
(initWithEntity:insertIntoManagedObjectContext:).
And in initWithEntity:insertIntoManagedObjectContext:'s documentation:
Important: This method is the
designated initializer for
NSManagedObject. You must not
initialize a managed object simply by
sending it init.

Accessing a property in NSManagedObject causes memory spike and crash

I am writing an iPhone app which uses core data for storage. All of my NSManagedObject subclasses has been automatically generated by xcode based on my data model. One of these classes looks like this:
#interface Client : NSManagedObject
{
}
#property (nonatomic, retain) NSNumber * rate;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSString * description;
#property (nonatomic, retain) NSSet* projects;
#end
Creating and saving new instances of this class works just fine, but when I try to access the 'description' property of such an instance, the program unexpectedly quits. When running in Instruments, I can see that just before the crash, a lot of memory is rapidly allocated (which is probably why the app quits).
The code where the property is accessed looks like this:
self.clientName = [[client.name copy] autorelease];
self.clientRate = [[client.rate copy] autorelease];
self.textView.text = client.description; // This is where it crashes
Note that the other properties (name and rate) can be accessed without a problem.
So what have I done wrong?
From the Apple documentation (Core Data programming guide):
Note that a property name cannot be the same as any no-parameter method name of NSObject or NSManagedObject, for example, you cannot give a property the name “description” (see NSPropertyDescription).
As noted by jbrennan, this should be causing the issue you are experiencing.