Release of NSManagedObject - iphone

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

Related

I'm trying to pass a string from my first ViewController to my second ViewController but it returns NULL

In my first view controller I have 3 input fields each of them take the user input into and saves it into a string such as: address, username and password as NSUserDefaults. This part works fine.
In my second view controller I'm trying to take the 3 strings from first controller (address, username and password) create a html link based on the 3 strings. I've tried many ways to access the 3 strings with no luck, the result I get is NULL.
Here is my code:
//.h file - first view controller with the 3 input fields CamSetup.h
#import <UIKit/UIKit.h>
#interface CamSetup : UIViewController <UITextFieldDelegate>
{
NSString * address;
NSString * username;
NSString * password;
IBOutlet UITextField * addressField;
IBOutlet UITextField * usernameField;
IBOutlet UITextField * passwordField;
}
-(IBAction) saveAddress: (id) sender;
-(IBAction) saveUsername: (id) sender;
-(IBAction) savePassword: (id) sender;
#property(nonatomic, retain) UITextField *addressField;
#property(nonatomic, retain) UITextField *usernameField;
#property(nonatomic, retain) UITextField *passwordField;
#property(nonatomic, retain) NSString *address;
#property(nonatomic, retain) NSString *username;
#property(nonatomic, retain) NSString *password;
#end
//.m file - first view controller CamSetup.m
#import "CamSetup.h"
#interface CamSetup ()
#end
#implementation CamSetup
#synthesize addressField, usernameField, passwordField, address, username, password;
-(IBAction) saveAddress: (id) sender
{
address = [[NSString alloc] initWithFormat:addressField.text];
[addressField setText:address];
NSUserDefaults *stringDefaultAddress = [NSUserDefaults standardUserDefaults];
[stringDefaultAddress setObject:address forKey:#"stringKey1"];
NSLog(#"String [%#]", address);
}
-(IBAction) saveUsername: (id) sender
{
username = [[NSString alloc] initWithFormat:usernameField.text];
[usernameField setText:username];
NSUserDefaults *stringDefaultUsername = [NSUserDefaults standardUserDefaults];
[stringDefaultUsername setObject:username forKey:#"stringKey2"];
NSLog(#"String [%#]", username);
}
-(IBAction) savePassword: (id) sender
{
password = [[NSString alloc] initWithFormat:passwordField.text];
[passwordField setText:password];
NSUserDefaults *stringDefaultPassword = [NSUserDefaults standardUserDefaults];
[stringDefaultPassword setObject:password forKey:#"stringKey3"];
NSLog(#"String [%#]", password);
}
- (void)viewDidLoad
{
[addressField setText:[[NSUserDefaults standardUserDefaults] objectForKey:#"stringKey1"]];
[usernameField setText:[[NSUserDefaults standardUserDefaults] objectForKey:#"stringKey2"]];
[passwordField setText:[[NSUserDefaults standardUserDefaults] objectForKey:#"stringKey3"]];
[super viewDidLoad];
}
#end
//.h second view controller LiveView.h
#import <UIKit/UIKit.h>
#import "CamSetup.h"
#interface LiveView : UIViewController
{
NSString *theAddress;
NSString *theUsername;
NSString *thePassword;
CamSetup *camsetup; //here is an instance of the first class
}
#property (nonatomic, retain) NSString *theAddress;
#property (nonatomic, retain) NSString *theUsername;
#property (nonatomic, retain) NSString *thePassword;
#end
//.m second view LiveView.m file
#import "LiveView.h"
#interface LiveView ()
#end
#implementation LiveView
#synthesize theAddress, theUsername, thePassword;
- (void)viewDidLoad
{
[super viewDidLoad];
theUsername = camsetup.username; //this is probably not right?
NSLog(#"String [%#]", theUsername); //resut here is NULL
NSLog(#"String [%#]", camsetup.username); //and here NULL as well
}
#end
There are 5 issues in this code:
If you are using ARC, all the "retain" in your #properties should be changed to "strong"
You name your iVars and properties the same thing. (common bad practice)
You are always directly accessing iVars and not properties in your code.
You don't retain your instance of CamSetup in the second object.
The direct cause of your problem: in the second object you've only created a placeholder for a CamSetup instance, you've not created one nor passed one to it! self.camSetup in your second object is empty right now.
Let's go step by step:
First, give your iVars different names from your properties. This is best practice, especially for a beginner! :)
#interface CamSetup : UIViewController <UITextFieldDelegate>
{
NSString *_address;
}
#property(nonatomic, strong) NSString *address;
#end
#implementation CamSetup
#synthesize address=_address;
...
#end
This is important, because you've setup properties, but in your code, you are not using them, you are directly accessing your iVars. Since you've named them the same thing, you might not see this.
Let's look at your first object. Every "address" in your code is going to your iVar and not property. Generally, you want to access the iVars via your properties unless you're sure otherwise. The #synthesize creates a getter and setter method for your iVar that will retain the var because you told it to in your #property statement. However, when you directly access your iVar's you're not going through those accessors and thus the stuff you wrote in your #properties doesn't matter. You could end up misunderstanding a lot of errors and bugs if you aren't clear about this. In your first object this worked anyway because the alloc/init sets a retain on the object, but I noticed you always do this, and that's going to get you into trouble.
Here's what the saveAddress: method would look like using properties:
-(IBAction) saveAddress: (id) sender
{
self.address = [[NSString alloc] initWithFormat:self.addressField.text];
[self.addressField setText:self.address];
NSUserDefaults *stringDefaultAddress = [NSUserDefaults standardUserDefaults];
[stringDefaultAddress setObject:self.address forKey:#"stringKey1"];
NSLog(#"String [%#]", self.address);
}
Next, in your second object you need to set properties for the CamSetup instance! Right now, you just have an iVar.
#import <UIKit/UIKit.h>
#import "CamSetup.h"
#interface LiveView : UIViewController
{
NSString *_theAddress;
NSString *_theUsername;
NSString *_thePassword;
CamSetup *_camSetup; //here is an instance of the first class
}
#property (nonatomic, retain) CamSetup *camSetup; // in synthesize we'll specify that this property uses the _camSetup iVar
#property (nonatomic, strong) NSString *theAddress;
#property (nonatomic, strong) NSString *theUsername;
#property (nonatomic, strong) NSString *thePassword;
#end
The implementation:
#import "LiveView.h"
#interface LiveView ()
#end
#implementation LiveView
#synthesize camSetup = _camSetup;
#synthesize theAddress = _theAddress;
#synthesize theUsername = _theUsername;
#synthesize thePassword = _thePassword;
- (void)viewDidLoad
{
[super viewDidLoad];
self.theUsername = self.camSetup.username; //this is probably not right?
// well, it's better now, but where is camSetup coming from??? it's undefined now
NSLog(#"String [%#]", self.theUsername); //resut here is NULL
NSLog(#"String [%#]", self.camSetup.username); //and here NULL as well
}
#end
We've created an iVar and property pair that will hold a pointer to a CamSetup instance and will retain that pointer (if we set it using the property). However, where is that CamSetup instance being created? Where do you alloc/init it?
There are many possible answers to this question. If CamSetup had getters for address, username, password that read them back in from your user defaults, then all you'd have to do is alloc/init a CamSetup and set it to camSetup. However, right now your first object has no functionality to retrieve the saved data so we can't do that. (still, this is the solution I'd hope you'd implement).
You might be initializing both of these in your app delegate? However, if you are using storyboard then likely it is initializing these object for you when it initializes your interface. In this case, in your appDelegate app has finished launching... method, you'll have to retrieve pointers to these instances, then the camSetup property on your second object, to point to the first. To tell you how to do this, we'd have to know detailed specifics of your app. Still, this wouldn't be doing it the best way.
Best practice would be to create an object which saves and retrieves these data from user defaults for you. This future proofs your implementation should you later want to change the way you store these data. You'd just change the way they are stored/retrieved within their class.
Your problem is here:
CamSetup *camsetup; //here is an instance of the first class
You aren't doing anything to make camsetup refer to the same instance of the CamSetup class that is taking input from the user. The camsetup variable is never initialized, hence it's NULL, hence any properties you try to retrieve from it will also be NULL.
How exactly you'd fix this depends on the structure of your program. One possibility is to have whatever code is creating the LiveView controller set the camsetup value. For example:
LiveView *liveViewController = [LiveView alloc] initWithNibName:#"LiveView" bundle:nil]];
liveViewController.camsetup = camSetupController;
(you'd need to make camsetup a property to do this).
BUT, from a design standpoint, having the one view controller have a reference to the other is probably the wrong way to go about solving this problem -- it introduces unnecessary dependencies between the two controllers. For example, say you later decide to make it possible to go directly to the LiveView controller upon program launch, using a saved name/password; you can't do that if LiveView depends on getting its input from a CamSetup object.
So, a better approach might be to have LiveView take its input from NSUserDefaults, and/or by having the code that's calling LiveView set the username/password properties.
-- edit --
For example, retrieve the data from the user defaults like:
self.address = [[NSUserDefaults standardUserDefaults] objectForKey:#"stringKey1"];
Is the camsetup initialized? If not -- initialize it first.
There are many ways to do this, most of which depend on how the two controllers are related to each other (like how are you going from the first to the second).
However, in your case you don't need to pass the string at all, since you are saving them in user defaults. Just read the values of those defaults in your second controller.
Hi i got your problem just do one thing.
When you are setting value in NSUserDefaults it need synchronization so just synchronize it.
[stringDefaultUsername synchronize];
And retrieve data using
NSString *username = [[NSUserDefaults standardUserDefaults] objectForKey:#"stringKey2"];
Follow apple NSUserDefaults Class Reference for more info

Trying to save NSManagedObject in appDelegate

i`m having a lot of trouble when i try to save a NSManageObject in the delegate, and worst problems trying to read the objects.
My Delegate is something like this:
#class ViewController;
#class RootViewController;
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
NSManagedObjectModel *managedObjectModel;
NSManagedObjectContext *managedObjectContext;
NSPersistentStoreCoordinator *persistentStoreCoordinator;
Trips *trip;
Garage *car;
}
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) ViewController *viewController;
#property (strong, nonatomic) RootViewController *rootviewcontroller;
#property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
#property (nonatomic, retain) Trips *trip;
#property (nonatomic, retain) Garage *car;
- (NSString *)applicationDocumentsDirectory;
- (void) setCar:(Garage *)Car;
- (void) setTrip:(Trips *)Trip;
- (Trips *) gettrip;
- (Garage *) getcar;
#end
The complete methods to set and get are like this:
- (void) setCar:(Garage *)Car
{
car = Car;
}
- (void) setTrip:(Trips *)Trip
{
trip = Trip;
}
- (Trips *) gettrip
{
return trip;
}
- (Garage *) getcar
{
return car;
}
In the first view when i do:
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
trip = (Trips *)[NSEntityDescription insertNewObjectForEntityForName:#"Trips" inManagedObjectContext:context];
[appDelegate setTrip:trip];
Seems to work OK, even if i do NSLog(#"%#", [appDelegate gettrip]); , it shows me the object without problemas. But when I try to read the same trip with [appDelegate gettrip] in other view, dosen`t work at all, in fact with NSLog it says that the object is the type Garage instead of Trips.
I don`t know what am i doing wrong. Help.
Are you making sure to save the context after you are creating the item? If you do not save it then I do not think that your new entity with be properly saved. In the code you are simply creating the object in the context, but you must save the context for its permanence:
NSError *error = nil;
if(![self.managedObjectContext save:&error]){
//Handle error
}
I hope this helps!
If NSLog is reporting a different object then you need to look at where you are creating a Garage entity.
Perhaps you have a simple copy and paste error?
while you are creating the object to be saved it doesn't look like you are actually saving the object to the persistent storage.
try this:
if (![managedObjectContext save:&error]) {
NSLog(#"ERROR: %#", [error userInfo]);
} else {
NSLog(#"EVERYTHING SHOULD HAVE SAVED PROPERLY");
}
now when you read the managedObject in the other views it should be there.
Is it a memory management problem with your custom setters?
Do they need to be
- (void)setTrip:(Trip *)Trip {
self.trip = Trip;
}
i.e. self.trip = instead of just trip = to use the property - otherwise I don't think it will retain your trip object and it will get autoreleased. Then, when yo come to use it later something else will be in that memory location.
Disclaimer - I'm still not 100% up with ARC yet so I might be wrong. It's worth a try though!

Core Data Strings

Im new to using core data and having really basic problems. Im trying to have the user enter a string and then be able to save that string and allow it to be returned to them at some point. But i cannot seem to get it to save. In fact the program quits when I attempt to run the following method. I can post the rest of my project, but i thought maybe that would be annoying so let me know if seeing it in greater detail would help. Thanks so much.
James
.h: file
#import <UIKit/UIKit.h>
#import "People.h"
#class rootViewController;
#interface data : UIView <UITextFieldDelegate>{
rootViewController *viewController;
UITextField *firstName;
UITextField *lastName;
UITextField *phone;
UIButton *saveButton;
NSMutableDictionary *savedData;
//Used for Core Data.
NSManagedObjectContext *managedObjectContext;
NSMutableArray *peopleArray;
}
#property (nonatomic, assign) rootViewController *viewController;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain) NSMutableArray *eventArray;
- (id)initWithFrame:(CGRect)frame viewController:(rootViewController *)aController;
- (void)setUpTextFields;
- (void)saveAndReturn:(id)sender;
- (void)fetchRecords;
#end
.m file:
-(void)saveAndReturn:(id)sender{
People *userEnteredName = (People *)[NSEntityDescription insertNewObjectForEntityForName:#"People" inManagedObjectContext:managedObjectContext];
[userEnteredName setName:firstName.text];
//NSError *error;
//if (![managedObjectContext save:&error]) {
// This is a serious error saying the record could not be saved.
// Advise the user to restart the application
//}
[peopleArray insertObject:userEnteredName atIndex:0];
}
From the error you gave you must have named the People object differently - in the model are you using "People" for both class and entity name (those can be the same)?
Edit:
After reviewing your code, you had multiple problems:
1) In the app delegate you did "[data alloc]" but no init. That was where you set the managed object context, but it was never used... not just because of the lack of an init but because...
2) The place where the data controller was really built and used from was the rootViewController. That's the one that is actually doing all the work, the one in the app delegate is just discarded.
3) So where to get the context then? Honestly the best spot is in the data controller, one fix I know worked was putting this line before every time the context was accessed:
#import "UserProfileAppDelegate.h"
// Then in the method before the use of context........
self.managedObjectContext = [((UserProfileAppDelegate *)[[UIApplication sharedApplication] delegate]) managedObjectContext];
When that was in place, the project ran. I think though you should put that into something like a viewDidLoad on the data controller (if it has a view that is ever used).

Access a NSMutableArray of an object

I post this topic because I have a problem with my iPhone application since 3 days. I hope someone can help me because I'm going crazy.
Here is the thing : I fill in an object userXMLData,in the delegate of my application, with a XML Parser. This object contains many NSStrings and a NSMutableArrays which contains objects type Album to.
My problem is : I can display all data of userXMLData with an internal function, but when I'm trying to get the data from the array in my viewController , it doesn't work. I mean, it crashes. It's weird because I can access to the appDelegate.userXMLData.NSString but not of my appDelegate.userXMLData.NSMutableArray
Here is my code :
// Initializaiton in the delegate
userXMLData = [[UserXMLData alloc] init];
userXMLData.myArray = [[NSMutableArray alloc] init];
UserXMLData.h
#interface UserXMLData : NSObject {
// User Data
NSString *userId;
// Content
NSMutableArray *myArray;
}
#property(nonatomic, retain) NSString *myString;
#property(nonatomic, copy) NSMutableArray *myArray;
#end
//Album.h
#interface Album : NSObject {
NSString *albumId;
NSMutableArray *content;
}
#property(nonatomic, retain) NSString *albumId;
#property(nonatomic, retain) NSMutableArray *content;
#end
As I said, I really don't why it crashes. I'm stuck and I cannot continue my application without fixing it.
Enable Zombies by following the instructions here:
http://loufranco.com/blog/files/debugging-memory-iphone.html
This will cause your app to not release any objects and instead cause them to complain to the console if messages are sent to them after they are released.
The most common cause of a crash is releasing too often (or retaining too few times).
Also, running a Build and Analyze can sometimes point these out.
Would be able to answer better if you'd show the code where you are trying to access the array and the error you receive on crash, but I'd hazard a guess that you don't have #synthesize myArray in your implementation (.m) file

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.