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.
Related
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.
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.
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
I've noticed that some generated classes only declare class properties/variables via #property, and don't include them within the #interface, as such:
#interface AddItemViewController : UITableViewController {
}
#property (nonatomic, retain) UITextField *itemName;
I was just curious if that's an acceptable way to do it, or if that is done for different reasons?
I normally do this:
#interface AddItemViewController : UITableViewController {
UITextField *itemName;
}
#property (nonatomic, retain) UITextField *itemName;
I declare it first in the #interface and then add the #property for it...
* Update *
I just wanted to update this a bit, because it's still not 100% clear to me.
I always thought that to declare a #property, you first needed to declare it within the #interface first, and then I saw this:
#interface mInventoryAppDelegate : NSObject <UIApplicationDelegate> {
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#end
All of those #property declarations are declared only as #properties, and not within the #interface.
For example, if I had say NSString *myString - I can declare that in the #interface and not as a #property and still have access to it no problem, but the getters and setters won't be created. I could also declare it in both. But what if I just declare it as #property, as such:
#interface AddItemViewController : UITableViewController {
}
#property (nonatomic, retain) NSString *myString;
Notice how I didn't add it between the #interface { } - how does it differ.
Sorry for repeating, but I'm just trying to reword this so that I can get an answer that makes more sense to me.
With the "modern" runtime, which the iPhone uses, the compilers can create the instance variable for you. Just use:
#synthesize itemName;
or if you prefer...
#synthesize itemName=_itemName;
...in your implementation. The compilers will then create ivar 'itemName' or '_itemName'.
This is of course for the case that the property is a simple getter/setter for one particular instance variable.
EDIT: NVM, per #bbum, what I thought of in my mind as the "32-bit" sim is actually the older simulator that didn't behave like the new runtime. The newer simulator is still 32-bit, and supports this behavior. See his comment below.
update
In response to your updated question:
The "interface" for a class is everything up to the #end. I think what you are calling "interface" is actually just the instance variables within the {}. What is between the {} are the instance variables for your class. The whole #interface includes those instance variables PLUS the method and #property declarations between the {} and the #end.
So I think what you are really asking is if you have a #property in your #interface, and that #property is just a simple getter/setter pair, then do you need to declare a "backing" instance variable also in your #interface, within the {}.
The answer for iPhone is NO. The compilers (both) can create that instance variable for you.
I hope that answers the question?
It is perfectly acceptable to do it this way. You would however need to implement the setter/getter methods yourself. These can not be created using the #synthesize syntax.
One reason to use this approach could be to have the properties based on something more complex than just setting and getting a value. It doesn't however make much sense for simple Nib connections as in your example.
In my code I use initWithArray like so :
vendorArray = [[NSMutableArray alloc] initWithArray:resultsCore.availableVendors];
But changes I make to the vendorArray also seem to be made to restultsCore.availableVendors.
This was not what I had desired.
I want populate vendorArray with restultsCore.availableVendors but then leave it untouched by the changes I make to vendorArray.
Is there away to get this functionality without using an for and if statements to copy the data object by object into vendorArray from restultsCore.availableVendors ?
Many Thanks
-Code
EDIT Below is my edit.
Vendors is an object i made
#interface Vendor : NSObject
{
NSString *vendorID;
NSMutableArray *availableCars;
BOOL atAirport;
}
#property (nonatomic, copy) NSString *vendorID;
#property (nonatomic, retain) NSMutableArray *availableCars;
#property (nonatomic, assign) BOOL atAirport;
- (id)initFromVehVendorAvailsDictionary:(NSDictionary *)vehVendorAvails;
#end
As you can see it has an array.
This array is another object I made called Car.
#interface Car : NSObject
{
NSMutableArray *vehicleCharges; // These are extras like breakdown assist etc
NSString *rateQualifier;
BOOL needCCInfo;
NSMutableArray *fees; // This array contains 3 dictionarys
NSMutableArray *pricedCoverages;
}
#property (nonatomic, retain) NSMutableArray *vehicleCharges;
#property (nonatomic, copy) NSString *rateQualifier;
#property (nonatomic, assign) BOOL needCCInfo;
#property (nonatomic, retain) NSMutableArray *fees;
#property (nonatomic, retain) NSMutableArray *pricedCoverages;
- (id) initFromVehicleDictionary:(NSDictionary *)vehicleDictionary;
#end
Car also has 3 arrays of my other objects. Its all very object oriented -.-
I've been reading up on NSCopying and saw a tutorial. But I still feel really lost as what to do here.
Do I need to implement the NSCopying protocol for my Vendors class, Car class and the other 3 classes that are contained in the arrays by car.
Vendors is the object contained in the array i want to copy. So is this the only class i need to implement the NSCopying tutorial for?
Many thanks, sorry for the long question
-Code
vendorArray = [[NSMutableArray alloc] initWithArray:resultsCore.availableVendors copyItems:YES];
Don't forget to implement the NSCopying protocol in the classes for the objects to be copied.