should variable be retained or not? iphone-sdk - iphone

in the following piece of code I got from a book.
The NSString *pPath which is defined in the class as an instance variable.
#interface MainViewController : UIViewController {
NSString *pPath;
}
In the implementation after being set it is being retained. I assume that with the assignment the object is automatically retained (because it is an NSString) and there is no need to additionally retain it.
- (void) initPrefsFilePath {
NSString *documentsDirectory =
[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
pPath = [documentsDirectory stringByAppendingPathComponent:
#"flippingprefs.plist"];
[pPath retain];
}

Yes, you need to retain your pPath variable if you obtain it this way. However it is not the end of the story - you also need to release its previous value otherwise it will just leak.
To make things easier you can use objective-c properties that allow you to automatically generate setter/getter methods with desired memory management behavior:
// header
#interface MainViewController : UIViewController {
NSString *pPath;
}
#property (nonatomic, retain) NSString* pPath;
// implementation
#synthesize pPath;
- (void) initPrefsFilePath {
NSString *documentsDirectory =
[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
self.pPath = [documentsDirectory stringByAppendingPathComponent:
#"flippingprefs.plist"];
}
Here in self.pPath=... line automatically generated setter method will get called which:
Will release previously set pPath value
Will assign new value to pPath and retain it
You will also need to release your pPath variable in dealloc method:
-(void) dealloc{
[pPath release];
//or
self.pPath = nil;
[super dealloc];
}

Related

Getting Properties Set in Constructor

I am new to Objective-C. I am trying to get a property set in the constructor, but am getting an EXC_BAD_ACCESS error. Here is my constructor:
- (id) init {
self = [super init];
if (self != nil) {
appFolderPath = [[NSBundle mainBundle] resourcePath];
fileManager = [NSFileManager defaultManager];
mediaArray = [fileManager directoryContentsAtPath: [appFolderPath stringByAppendingString:#"/Media/Silly"]];
mediaIndex = 0;
}
return self;
}
Here are my properties:
#property (retain) NSFileManager* fileManager;
#property (retain) NSString* appFolderPath;
#property int mediaIndex;
#property (retain) NSArray* mediaArray;
Any ideas?
You have the retain keyword on your properties, which is good. But, it doesn't matter because you are not actually using them. You are accessing the ivars directly, bypassing the getter method that the Objective-c compiler generated for you.
To contrast between and ivar and a property, see this code:
MyInterface.h
#interface MyInterface : NSObject {
#private
NSFileManager * fileManager; // This is an instance variable, or 'ivar'
}
#property (retain) NSFileManager * fileManager; // This is the declaration of a property
MyInterface.m
#implementation MyInterface {
#synthesize fileManager;
// Calling this causes the Objective-C compiler to generate the following
// methods for you:
// -(NSFileManager *) getFileManager ...
// and - (void) setFileManager: (NSFileManager *) val ...
}
To use the ivar from your class you would simply reference its name, in your example you did that with the line:
fileManager = [NSFileManager defaultManager];
Since the auto-released instance returned by the method you called isn't being retained, you get an EXEC_BAD_ACCESS exception later on in your program. To use the property you need to preface it with the owning object reference:
self.fileManager = [NSFileManager defaultManager];
This ensures that your ivar is set and retained.
EDIT
Now, to really appreciate the difference between an instance variable and a property, you could have declared your interface as:
#interface MyInterface : NSObject {
#private
NSFileManager * _fileManager;
}
#property (retain) NSFileManager * fileManager;
And in your .m you would have:
#synthesize fileManager = _fileManager.
Now when you tried to do fileManager = [NSFileManager defaultManager]; your code would not compile, because there is no ivar called fileManager in your class. It's a useful coding style to avoid common mistakes.
#Perception already explained the ins and outs of why this doesn't work. Here's how to rectify this. Since you're not using the synthesized setters which would send the retain message, you're forced to handle it yourself when directly accessing the instance variables. Also note that objects of type NSString should be copied instead of retained, so the declaration of appFolderPath should actually be #property (nonatomic, copy) NSString *appFolderPath;. Taking all of this into account, your -init should look something like this.
- (id)init
{
self = [super init];
if (self != nil)
{
appFolderPath = [[NSBundle mainBundle] resourcePath] copy];
fileManager = [NSFileManager defaultManager] retain];
mediaArray = [fileManager directoryContentsAtPath:[appFolderPath stringByAppendingString:#"/Media/Silly"]] retain];
mediaIndex = 0;
}
return self;
}
You aren't assigning to properties, you're assigning to instance variables. If you want to use the properties, you need to prefix the names with self., as in self.fileManager.
The effect is that the objects aren't retained, and probably get deallocated later on. When you then later try to access the properties (or their instance variables) the objects are already gone. Sometimes there's a different object in their place, sometimes it's garbage. And that's when you get the crash.
I don't want to step on any toes - #Perception has provided a thorough explanation and then #Mark gave the actual solution.
Here's the quick version:
This
#property (nonatomic, copy) NSString* appFolderPath;
and matching
#synthesize appFolderPath = _appFolderPath;
mean that the compiler generates the following methods
- (void)setAppFolderPath(NSString *)appFolderPath;
- (NSString *)appFolderPath;
These methods will take care of the memory management of retaining/copying/referencing an object on assignment depending on which option you choose retain/copy/assign. This memory management will only come into effect if you use
[self setAppFolderPath:#"My Folder Path"];
or
self.appFolderPath = #"My Folder Path"; // which compiles to the previous call
Now this is all well and good for general use in your class but sometimes it is recommended to not use getters and setters in case they cause side effects. Two places where you normally want to avoid getters and setters are in init methods and dealloc.
Therefore as you was in the init method you should access the ivar directly without using the getters/setters. This means that you will need to perform the memory management yourself. e.g.
- (id)init
{
self = [super init];
if (self) {
_appFolderPath = [[[NSBundle mainBundle] resourcePath] copy];
}
return self;
}
In the dealloc method you would access the ivar directly again like this
- (void)dealloc
{
[_appFolderPath release];
[super dealloc];
}
As #Mark pointed out you generally copy strings so that they can't be changed underneath you.
Additional notes on examples.
When I called #synthesize appFolderPath = _appFolderPath; It creates the getters and setters using the name appFolderPath but the ivar these methods effect is actually called _appFolderPath. This is good practice as you will always be sure when you are accessing the ivar directly or through a getter/setter because just referencing appFolderPath in your code will not compile.

How to save a NSMutableArray (containing other arrays) to file

This has been asked before and people have given very good instructions on how to do this, e.g. here.
However, I was wondering if I really need to work with NSCoder if I simply wanted to save one NSMutableArray (containing various instances of another NSMutableArray) to a file? I tried this but only got an error message:
-(void)saveLibraryDat {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents directory
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"myLibrary.dat"];
NSError *error;
[myLibrary writeToFile:filePath atomically:YES];
if (error) {
NSLog(#"There was an error saving myLibrary.dat: %#", error);
}
}
My error message:
2011-05-13 22:00:47.840 MoleNotes[15437:207] There was an error saving myLibrary.dat: (
1,
2
)
So I guess I have to work with NSCoder, right? If so, I was wondering how to go about this. People have explained how to do this with a class, but in my case, I have a NSMutableArray (myLibrary) which contains various instances of a class. Will I have to implement the NSCoder in this class and the NSMutableArray?
I alloc my library like this:
myLibrary = [[NSMutableArray alloc] init];
And then add instances of a class called NoteBook.m like this:
NoteBook *newNoteBook = [[NoteBook alloc] init];
newNoteBook.titleName = #"Notes"; // etc.
[myLibrary addObject:newNoteBook];
So where exactly do I put the NSCoder commands? Only into my NoteBook.m class? Will this automatically take care of myLibrary?
Thanks for any suggestions.
EDIT:
So I've updated my code, but I guess the big problem is that my NSMutableArray myLibrary contains several instances of a custom class I've set up (called notebook). I have set up NSCoding for this class (and all its variables) so that I can save it and load it.
Now my app works totally fine if I create the NSMutableArray in the app (i.e. when the app is started for the very first time, no file exists), instead of loading it from disk:
-(void) setupLibrary {
myLibrary = [[NSMutableArray alloc] init];
NoteBook *newNoteBook = [[NoteBook alloc] init];
newNoteBook.titleName = #"Notes";
/...
If I load it from disk, it works fine as well:
-(void)loadLibraryDat {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents directory
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"myLibrary.dat"];
myLibrary = [[NSMutableArray alloc] init];
myLibrary = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
if (!myLibrary) {
// if it couldn't be loaded from disk create a new one
NSLog(#"myLibrary.dat empty... set up new one");
[self setupLibrary];
} else { NSLog(#"Loading myLibrary.dat successful."); }
}
If I log everything which is contained in my library after loading it, everything is still fine. E.g. the following works totally fine:
NSLog(#"%#", [[self.myLibrary objectAtIndex:0] titleName]);
The big problem is, however, if any other method tries to access myLibrary. For instance, if I call the very same log command from another method, the app will crash and I get this error message:
[NSCFString objectAtIndex:]: unrecognized selector sent to instance 0x4b38510
2011-05-14 14:09:10.490 Notes[17091:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSCFString objectAtIndex:]: unrecognized selector sent to instance 0x4b38510'
This sounds to me as if myLibrary has become deallocated somehow, but I can't see why. How could this have happened? I have the feeling that I did something wrong in my NSCoding set up... because if I simply create myLibrary in code, everything works like wonderfully. It's only if I load it from the disk, that the app will crash.
Here is the class setup:
#import <Foundation/Foundation.h>
#interface NoteBook : NSObject <NSCoding> {
NSString *titleName;
NSString *fileName;
NSMutableArray *tabTitles;
NSMutableArray *tabColours;
NSMutableArray *tabReference;
}
#property (nonatomic, retain) NSString *titleName;
#property (nonatomic, retain) NSString *fileName;
#property (nonatomic, retain) NSMutableArray *tabTitles;
#property (nonatomic, retain) NSMutableArray *tabColours;
#property (nonatomic, retain) NSMutableArray *tabReference;
-(id)initWithCoder:(NSCoder *)aDecoder;
-(void)encodeWithCoder:(NSCoder *)aCoder;
#end
//
// NoteBook.m
#import "NoteBook.h"
#implementation NoteBook
#synthesize titleName, fileName, tabTitles, tabColours, tabReference;
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.titleName = [aDecoder decodeObjectForKey:#"titleName"];
self.fileName = [aDecoder decodeObjectForKey:#"fileName"];
self.tabTitles = [aDecoder decodeObjectForKey:#"tabTitles"];
self.tabColours = [aDecoder decodeObjectForKey:#"tabColours"];
self.tabReference = [aDecoder decodeObjectForKey:#"tabReference"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:titleName forKey:#"titleName"];
[aCoder encodeObject:fileName forKey:#"fileName"];
[aCoder encodeObject:tabTitles forKey:#"tabTitles"];
[aCoder encodeObject:tabColours forKey:#"tabColours"];
[aCoder encodeObject:tabReference forKey:#"tabReference"];
}
#end
EDIT:
I think I've solved it... I forgot a little 'self'... which messed it all up and deallocated myLibrary:
self.myLibrary = [NSKeyedUnarchiver
unarchiveObjectWithFile:filePath];
if (self.myLibrary == nil) {
NSLog(#"myLibrary.dat empty... set up new one");
[self setupLibrary];
} else { NSLog(#"Loading myLibrary.dat successful."); }
Your code is busted. The "error" variable is uninitialized and never set, so when you check it, you're just seeing random garbage data. If you want to know whether the write was successful, check the return value of writeToFile:atomically:. It will be YES if the write succeeded and NO if it didn't.
However, NSArray's writeTo… methods are for creating plists. If non-property-list objects are in your array, that method isn't appropriate, and an archiver is what you want. Just do something like [[NSKeyedArchiver archivedDataWithRootObject:myLibrary] writeToFile:writeToFile:filePath atomically:YES].
To make your objects conform to NSCoding correctly, just have them implement initWithCoder: and encodeWithCoder:, and in those methods, use NSCoder's storage methods to store the object's instance variables (and the retrieval methods to get them back out).
NSCoder is a protocol that your class must conform to in order to be archived to data/file. Works something like Serealizabe in Java.
Add conformance to the class header like this:
#interface NoteBook : NSObject <NSCoder> { // …
And then you must implement two methods:
-(id)initWithCoder:(NSCoder)decoder;
{
self = [super initWithCoder:decoder];
if (self) {
_someIvar = [decoder decodeObjectForKey:#"someKey"];
// And more init as needed…
}
return self;
}
-(void)encodeWithCoder:(NSCoder)coder;
{
[super encodeWithCoder:coder];
[coder encodeObject:_someIvar forKey#"someKey"];
/// Etc…
}
I would also advice against using -[NSArray writeToFile:atomically:] since in work with property list compliant objects only, not coding compliant classes. The property list object are NSString, NSData, NSArray, or NSDictionary, NSDate, and NSNumber. The list can not be extended.
Instead use NSKeyedArchiver/NSKeyedUnarchiver. Almost as simple to use:
if (![NSKeyedArchive archiveRootObject:yourArrat toFile:path]) {
// It failed.
}

Why NSString variable needs to be retained?

I have the following code in my .h file:
#interface Utils : NSObject {
NSString *dPath;
}
#property(nonatomic, retain) NSString *dPath;
And in my .m file:
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
dPath = [[documentPaths objectAtIndex:0] stringByAppendingPathComponent:kDatabaseName];
[dPath retain];
Why do I have to retain dPath if it's already defined as (nonatomic, retain)?
If I don't add the [dPath retain]; I get some strange, random errors and the application crashes when using this variable in other functions. I guess that's because of some autorelease somehere but I don't have any.
So, what is the (nonatomic, retain) doing anyway? Is it really necessary the [dPath retain]; or am I just hiding something else with that?
Because the code isn't calling the dPath property's setter method, it's just setting the instance variable dPath directly:
dPath = [[documentPaths objectAtIndex:0] stringByAppendingPathComponent:kDatabaseName];
[dPath retain];
So it has to be retained manually.
You will be able to (in fact you need to) omit the retain call if the property setter was used like this (notice the self.):
self.dPath = [[documentPaths objectAtIndex:0] stringByAppendingPathComponent:kDatabaseName];
or like this (notice the setDPath:):
[self setDPath:[[documentPaths objectAtIndex:0] stringByAppendingPathComponent:kDatabaseName]];
The setter retains the NSString for you so you don't have to do it yourself.
A nice little practice to follow in order to avoid the confusion, is to affix an underscore to your ivar name to indicate it's an ivar:
NSString *dPath_;
Then synthesize your property like this, to associate it with your differently-named ivar:
// self.dPath is the property, dPath_ is the ivar
#synthesize dPath = dPath_;
Then modify your dealloc method, as well as any other code that directly references the instance var, to use the affixed name instead:
- (void)dealloc {
[dPath_ release];
[super dealloc];
}
try setting and getting it with
self.dPath
If you want to call the property setter method, which will invoke the retain, then you want to write:
self.dPath = ...
Jamming stuff into a variable with:
dPath = ...
completely ignores the properties of this instance variable. Which is why you ended up needing to do the retain manually.

Variable losing its value (iPhone SDK)

I declare a variable in the header file and synthesize it in the implementation. When the view loads (ViewDidLoad) I read a plist file an populate the variable with a value. WIth my NSLog I see that the variable contains the value. However, after the view loads, I have some interaction with the user via a button the executes a method. WIthin that method I check the value again and it is invalid. Why won't the variable maintain its value after the initial load?
program.h
....
NSString * user_title;
...
#property (nonatomic, retain) NSString *user_title;
program.m
#synthesize user_title;
-(void)viewDidLoad{
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
NSArray *array = [[NSArray alloc] initWithContentsOfFile:filePath];
user_title = [array objectAtIndex:0];
[array release];
}
....
-(IBAction)user_touch_screen:(id)sender
{
user_label.text = user_title; //user_title has an invaliud value at this point
....
user_title = [array objectAtIndex:0] doesn't retain the variable.
Use this instead:
self.user_title = [array objectAtIndex:0];
That will use the setter that you synthesized, and will retain the value.
You need to retain the value you get out of the array.

How am I leaking memory?

I have a table view that when loading creates a person object
Person.h
#import <UIKit/UIKit.h>
#import "TwitterHelper.h"
#interface Person : NSObject {
NSDictionary *userInfo;
NSURL *image;
NSString *userName;
NSString *displayName;
NSArray *updates;
}
/*
#property (retain) NSString *userName;
#property (retain) NSString *displayName;
#property (retain) NSDictionary *userInfo;
*/
#property (nonatomic, copy) NSURL *image;
#property (retain) NSArray *updates;
- (id)initWithUserName:userName;
#end
Person.m
#import "Person.h"
#implementation Person
/*
#synthesize userName;
#synthesize displayName;
#synthesize userInfo;
*/
#synthesize image;
#synthesize updates;
- (id)initWithUserName:(NSString *)user{
userName = user;
userInfo = [TwitterHelper fetchInfoForUsername:user];
displayName = [userInfo valueForKey:#"name"];
image = [NSURL URLWithString:[userInfo valueForKey:#"profile_image_url"]];
updates = [TwitterHelper fetchTimelineForUsername:userName];
return self;
}
- (void)dealloc
{
/*
[userName release];
[displayName release];
[updates release];
[userInfo release];
[image release];
*/
[super dealloc];
}
#end
Inside my UITableView method cellAtRowForIndexPath I am creating each person object and assigning the image property like so...
Person *person = [[Person alloc] initWithUserName:userName];
NSData *data = [[NSData alloc] initWithContentsOfURL:person.image];
[data release];
When I run this in Instruments it highlights the NSData *data... row saying that is where the leak is.
Why is it leaking there?
First, you need to understand the difference between instance variables and properties and getter/setters.
instance variables (ivars) are variables stored in
your object. You access an ivar from within a method simply by naming it (eg "userName").
properties define an
interface to your object, allowing
information to be read and/or written
to your object.
getters/setters implement that interface and may use an ivar as backing storage
You access a property by using a getter/setter, either explicitly (eg [self userName]) or (equivalently) using dot syntax self.userName. Note that these two notations are exactly identical. You declare a property (ie, you declare an interface to your object) using #property in the interface of your object, something like:
#property (copy) NSString* userName;
This declartion is essentially equivalent to typing:
- (NSString*) userName;
- (void) setUserName: (NSString*) theUserName;
You implement a property, either by using #synthesize (which simply tells the compiler to write the getter/setter for you) or by implementing it yourself (ie, you write methods implementation for userName and setUserName). There is also a rarely used third option, #dynamic, which tells the compiler you will handle the methods at run time, essentially just silincing the warning you would otherwise get.
Next, you need to read and understand the memory management rules. Its only 9 short paragraphs, go read it now, I'll wait. Done? good.
Further, you need to know that you should not use getters/setters in either the init or dealloc routines.
So your init routine should look something like this:
- (id)initWithUserName:(NSString *)user{
userName = [user copy];
userInfo = [[TwitterHelper fetchInfoForUsername:user] retain];
displayName = [[userInfo valueForKey:#"name"] copy];
image = [[NSURL URLWithString:[userInfo valueForKey:#"profile_image_url"]] copy];
updates = [[TwitterHelper fetchTimelineForUsername:userName] retain];
return self;
}
Note that you take ownership of each value you store in an ivar with retain or copy. Generally, you use copy for NSString to convert an NSMutableStrings into NSStrings you own, rather than retain which would leave you holding a reference to a possibly mutable string. The same issue applies to NSArray/NSDictionary, but we will assume TwitterHelper intends to hand off the fetched data.
Your dealloc will have to release the various ivars:
- (void)dealloc
{
[userName release];
[displayName release];
[updates release];
[userInfo release];
[image release];
[super dealloc];
}
Anywhere else in your code you would use self.userName to access or change the properties, rather than access the ivars directly.
Note that you might consider not storing the displayName (and similarly image) at all, but simply implement a property getter that retrieves it from userInfo. To do this, delete the displayName ivar, change the property to:
#property (readonly) NSString *displayName;
remove the #synthesize displayName, and add a manual getter:
- (NSString*) displayName
{
return [userInfo valueForKey:#"name"];
}
and remove the release in dealloc.
Note that you do not need to retain/release the value in displayName - you return a value that the receiver does not own and it is up to them to copy/retain it if they want to keep it.
If you choose to create a property, you should use:
self.image = [NSURL URLWithString:[userInfo valueForKey:#"profile_image_url"]];
in your init message and not
image = [NSURL URLWithString:[userInfo valueForKey:#"profile_image_url"]];
Setting the value without the self prefix will not call the copy or retain message, and will create a memory problem (not necessarily a leak).
This might be what Instruments is pointing you to.
(This obviously applies to all properties!)
Alternatively, if you don't want to use the accessor, then retain or copy the value retrieved, e.g.:
image = [[NSURL URLWithString:[userInfo valueForKey:#"profile_image_url"]] retain];
You are calling alloc on Person but not releasing it. You've leaked your person object.
(in your cell configuration)