Objective C NSMutableDictionary memory management - iphone

I have a model class that keeps track record being built by multiple views. It has a NSMutableDictionary that has the fields and values I eventually write to the database. It is saved to a plist and loaded back when needed. I thought that I was keeping track of my memory, but it throws a EXC_BAD_ACCESS when I try to release the Dictionary. Here is my interface:
#import <Foundation/Foundation.h>
#interface CurrentEntryModel : NSObject {
NSMutableDictionary *currentEntry;
}
#property (nonatomic, retain) NSMutableDictionary *currentEntry;
- (void) setValue: (NSString *)value;
- (NSString *) getValue;
#end
My understanding is that currentEntry should be retained and I would have to release it during dealloc.
Here is my implementation (this isn't the entire class just the relevant parts):
#import "CurrentEntryModel.h"
#implementation CurrentEntryModel
#synthesize currentEntry;
-(id) init {
if ( self = [super init] )
{
//check for file
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *file;
file = #"location.plist";
if ([fileManager fileExistsAtPath:file]){
NSLog(#"file exists");
currentEntry = [[NSMutableDictionary alloc] initWithContentsOfFile:file];
}else {
NSLog(#"file doesn't exist");
currentEntry = [[NSMutableDictionary alloc ] initWithCapacity:1];
NSDate *testDate = [NSDate date];
[currentEntry setObject:testDate forKey:#"created"];
[currentEntry writeToFile:file atomically:YES];
}
}
return self;
}
- (void) setValue: (NSString *)value {
[currentEntry setObject:value forKey:#"location"];
}
- (NSString *) getValue {
return [currentEntry objectForKey:#"location"];
}
- (void) dealloc{
[currentEntry release];
[super dealloc];
}
#end
If I init this class it will automatically create the dictionary and if I call one of the set or get methods it seems like the dictionary is retained as it will dealloc correctly. If the class is just initialized and then no methods are called it will throw the EXC_BAD_ACCESS errors. If I am not mistaken when the file doesn't exist I don't initialize the dictionary correctly because the method starts with dictionary and not init. Although every time I run this the file is there so it always uses the the file found logic and I thought that that will retain the variable.
Am I not initializing the dictionary correctly?
Edit - changed the code on the convenience method to reflect the proper way. Everyone take note of what Squeegy has to say.

This is bad bad bad.
else {
NSLog(#"file doesn't exist");
currentEntry = [[NSMutableDictionary alloc ] dictionaryWithCapacity:1];
dictionaryWithCapacity: is a class method on NSMutableDictionary which returns an autoreleased object, and you don't retain it. So the run loop ends, and the dictionary gets autoreleased. Then you run [currentEntry release] in your dealloc and it explodes because that object is deallocated already.
you probably wan't initWithCapacity: instead. Always pair alloc with a method that starts with init.
Also, when using retained properties like this, I usually let the property figure this out for me, and only work with autoreleased objects. You just have to remember less rules, and there are less gotchas.
- (id)init {
// ...
self.currentEntry = [NSMutableDictionary dictionWithContentsOfFile:file];
// ...
}
- (void)dealloc {
//...
self.currentEntry = nil;
//...
}
This way you never have to call retain or release directly on the object. In my experience, this results in less confusing bugs. But it's also point of style among many ObjC programmer that not everyone agrees with.

Joshua -
+ (id)dictionaryWithCapacity:(NSUInteger)numItems
is a class method of NSDictionary. So when you call it, it should be:
[NSMutableDictionary dictionaryWithCapacity:1];
Not:
[[NSMutableDictionary alloc] dictionaryWithCapacity:1];
Further, [NSMutableDictionary dictionaryWithCapacity:] returns an autoreleased object. If you want to keep the dictionary as an ivar and not have it autoreleased on the next cycle of the run loop, you should call:
[currentEntry retain];
So, basically, change it to:
currentEntry = [[NSMutableDictionary alloc] initWithCapacity:1];
or:
currentEntry = [[NSMutableDictionary dictionaryWithCapacity:1] retain];
The first one probably makes more sense, since the connivence class methods were designed to be used when you wanted an autoreleased instance.

Related

Can't Figure Out How To Fix Memory Leaks on iPhone

I was running Leaks tool and discovered a massive leak in my Dictionary mutableDeepCopy but I can't figure out what's wrong with the code. Any suggestions?
#interface RootViewController : UIViewController{
NSDictionary *immutableDictionary;
NSMutableDictionary *mutableDictionary;
}
Here is the line of code that's highlighted in Instruments
self.mutableDictionary = [self.immutableDictionary mutableDeepCopy];
Here is the method for creating a mutable copy of a Dictionary
#interface NSDictionary(MutableDeepCopy)
-(NSMutableDictionary *)mutableDeepCopy;
#end
Here is method implementation, I've highlighted the code that Leaks saids is leaking 100%
- (NSMutableDictionary *) mutableDeepCopy {
NSMutableDictionary *dictionaryToReturn = [NSMutableDictionary dictionaryWithCapacity:[self count]];
NSArray *keys = [self allKeys];
for(id key in keys) {
id value = [self valueForKey:key];
id copy = nil;
if ([value respondsToSelector:#selector(mutableDeepCopy)]) {
copy = [value mutableDeepCopy];
} else if ([value respondsToSelector:#selector(mutableCopy)]) {
copy = [value mutableCopy]; //This is the Leak
}
if (copy == nil) {
copy = [value copy];
}
[dictionaryToReturn setValue:copy forKey:key];
}
return dictionaryToReturn;
}
You need to analyse this in light of Apple's Memory Management Rules.
Starting with this line:
self.mutableDictionary = [self.immutableDictionary mutableDeepCopy];
I would expect mutableDeepCopy to return an object I own, so at some point I need to release or autorelease it. e.g.
NSMutableDeepCopy* temp = [self.immutableDictionary mutableDeepCopy];
self.mutableDictionary = temp;
[temp release];
or
self.mutableDictionary = [[self.immutableDictionary mutableDeepCopy] autorelease];
So now we need to look at mutableDeepCopy. Because it has 'copy' in the name it needs to returned an "owned" object which, in practice means "forgetting" to release the returned object. You have already failed to do that when you create the returned object in the first line, since dictionaryWithCapacity: gives you an object you do not own. Replace it with
NSMutableDictionary *dictionaryToReturn = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
Now you own it.
It is important that you make your mutableDeepCopy obey the rules because it means you can treat the objects returned from mutableDeepCopy, mutableCopy and copy in exactly the same way. In all three cases you own the object copy that you insert into the array. Because you own it, you must release it or it'll leak as you found out. So, at the end of the loop, you need
[copy release];
That'll stop the leak.
How is your property declared? If is is retain or copy, then this doesn't leak.
Your problem is that the name mutableDeepCopy suggests that it returns a retained object, and not an autoreleased one as it actually does.
Edit:
And at the mutableDeepCopy itself, you need to release the copy variable after adding to the dictionary.
mutableCopy increments the retain count of the object, as does setValue:forKey:. This means that when dictionaryToReturn is dealloc'ed, the object that had mutableCopy called still has a retain count of one.
Try doing this instead:
copy = [[value mutableCopy] autorelease];

releasing and retaining NSString* properly

Hi all i'm still new to iPhone development, but had strong experience with other programming languages. The thing that makes me pull my hair out is the Obj-C memmory management and releasing / retaining properly. I know the concept, i know "once i understand it will be easy" but i'm not quite still there, and that makes me crazy. Here i have one simple piece of code with class and method, that simply add's one character to existing string that is synthesized so used as class proprety ... the class for example is called myClass ...
myClas.h
#interface myClass : NSObject {
#private
NSString* someCommonString;
}
#propery (retain, nonatomic) NSString* someCommonString;
myClass.m
...
#synthesize someCommonString;
- (id) init
{
self = [super init];
if(self)
{
someCommonString = [[NSString alloc] initWith String:#"one "];
}
}
- (NSString*) appendString:(NSString*) stringToAdd
{
NSString* result = [someCommonString stringByAppendingString: stringToAdd];
return result;
}
- (void) doTheJob
{
NSString* test1 = #"two ";
NSString* test2 = [[NSString alloc] initWithString: #"three "];
NSString* test3 = [NSString stringWithFormat:#"four "];
self.someCommonString = [self appendString:test1];
self.someCommonString = [self appendString:test2];
self.someCommonString = [self appendString:test3];
NSLog(#"%#", someCommonString);
}
- (void) dealloc
{
[someCommonString release];
[super release];
}
...
Ok, after i alloc myClass and execute the doTheJob method, i should have #"one two three four" in the someCommonString class proprety. I know this is working, but is also leaking as hell. test1, test2 and test3 are 3 ways of initialising NSString, and only the test2 should be released, this is quite self-explanatory, but im much more worried what happens when passing them as arguments to the appendString method. Because there i know i hawe a leak, but don't know how to handle 1. stringToAdd argument [should i worry about it in appendString method at all ?] 2. the result -> if i autorelease the result, i don't know at which point the result will be deallocated. 3. sommeCommonString in appendStringMethod, should i retain it, release it or leave it alone ?
Huh :)
At a first glance, it seems to me like you're not releasing test2. After you have appended it to your common string, you do not need to retain it anymore.
self.someCommonString = [self appendString:test1];
self.someCommonString = [self appendString:test2];
self.someCommonString = [self appendString:test3];
[test2 release];
The other two (test1 and test3) are autoreleased, so your thread will reclaim them at some point).
As far as your appendString: method is concerned, result is already autoreleased and in fact you could reduce your implementation to
return [someCommonString stringByAppendingString: stringToAdd];
someCommonString is not affected by the operation at all. stringByAppendingString: returns a new autoreleased string from the concatenation of self and stringToAdd.
Hope that helps
… but im much more worried what happens when passing them as arguments to the appendString method. Because there i know i hawe a leak, but don't know how to handle
1. stringToAdd argument [should i worry about it in appendString method at all ?] …
You don't have a leak in -appendString:. You are passing stringToAdd around without retaining it and that's okay here.
The result is autoreleased and you don't have to take any action on it.
2. the result -> if i autorelease the result, i don't know at which point the result will be deallocated.
The result is already autoreleased and will be released as soon as the current NSAutoreleasePool will be released. Til then you can pass it around without retaining.
3. sommeCommonString in appendStringMethod, should i retain it, release it or leave it alone ?
Leave it alone, it's managed by the accessors. But as fedmest (and you too) said: release test2.
Well there are few problems in your code, but basic problem is you need NSMutableString string, not NSString to make your code work.
in init method, correct code to initialize is,
someCommonString = [[NSMutableString alloc] initWithString:#"one "];
You have to return the object (self) from init, otherwise it will not work, like this.
return self;
If you wanted to append the string, it should be NSMutableString, not NSString.
[[self someCommonString] appendString:test1];
[[self someCommonString] appendString:test2];
[[self someCommonString] appendString:test3];
In dealloc method, you call dealloc method of super, not release the super. So correct it it like this.
[super dealloc];
There is no need to release test1 and test3, because they are autorelease.
I have witten the correct code, try this out.
#interface myClass : NSObject {
#private
NSMutableString* someCommonString;
}
#property (retain, nonatomic) NSMutableString* someCommonString;
- (void) doTheJob;
#end
#implementation myClass
#synthesize someCommonString;
- (id) init
{
self = [super init];
if(self)
{
someCommonString = [[NSMutableString alloc] initWithString:#"one "];
}
return self;
}
- (NSString*) appendString:(NSString*) stringToAdd
{
NSString* result = [someCommonString stringByAppendingString: stringToAdd];
return result;
}
- (void) doTheJob
{
NSString* test1 = #"two ";
NSString* test2 = [[NSString alloc] initWithString: #"three "];
NSString* test3 = [NSString stringWithFormat:#"four "];
[[self someCommonString] appendString:test1];
[[self someCommonString] appendString:test2];
[[self someCommonString] appendString:test3];
NSLog(#"%#", someCommonString);
[test2 release];
}
- (void) dealloc
{
[someCommonString release];
[super dealloc];
}
#end
This is a technique which might help you deal better with what is happening with the #synthesize directive. Change your header as follows
#interface myClass : NSObject {
#private
NSString* _bob;
}
#property (retain, nonatomic) NSString* someCommonString;
and your class file to
#synthesize someCommonString = _bob;
If you recompile your code you will get build errors. Fix these and you will suddenly be able to see exactly what you are accessing via the synthesized property and what you are directly accessing.
Additionally, as already mentioned, you need to release test2 in the doTheJob method.

Correct way to instantiate NSDictionary/NSArray in init without extra retains

I have numerous classes that use the various NSDictionary/NSArray collection classes as ivars but often I run into the problem of my collection class getting released before the containing class is released.
This seems to happen mostly with the collections classes and not with another model class (ie classes that I either created separately or other NS* non-collection classes).
Here are the two variations I've done and seen other people do:
#implementation ClassX
// myDictionary declared as a property in the .h file as this:
// #property (nonatomic, retain) NSMutableDictionary *myDictionary;
#synthesize myDictionary;
- (id)int
{
if (self = [super init])
{
// Option 1:
// If I don't instantiate and assign with 'self',
// myDictionary ivar will not be available
// at times in doSomething.
myDictionary = [NSMutableDictionary dictionary];
// Option 2:
// Doing this, however will keep the dictionary around.
// because I have invoked an extra retain on the dictionary
self.myDictionary = [NSMutableDictionary dictionary];
// Which one is more correct?
}
return self;
}
- (void)doSomething
{
// this will give the error about trying to invoke
// a method on an already released instance
[myDictionary objectForKey:#"myKey"];
}
- (void)dealloc
{
// If I did self.myDictionary in 'init', I then
// need to do this:
[myDictionary release];
[super dealloc];
}
#end
So which approach is the more correct way to hold an instance of NSDictionary within a class?
Option 2 is correct; Option 1 is wrong.
But you left out the best option: myDictionary = [[NSMutableDictionary alloc] init].
I recommend using
myDictionary = [[NSMutableDictionary alloc] init];
The memory is only within the scope of the method you're in if you call [NSMutableDictionary dictionary]. Once you leave the method, that memory goes with it which is why you need to alloc/init if you want to retain the values.
That's why you don't have to release if you don't encounter an alloc.
So for instance:
- (void) doSomething {
// Do not need to release this string
NSString *someText = #"Hello world!";
// You need to release this string:
NSString *otherText = [[NSString alloc] initWithString:#"Hello world!"];
[otherText release];
}
Edited: Removed self after #mipadi #st3fan and caught my mistake. Forgot to post the change. Thanks for keeping me accountable.

Array doesn't persist outside viewDidLoad

I'm facing a strange problem with NSUsrDefaults. Whenever I'm fetching the data from NSUserDefaults, it's getting populated temporarily. I'm fetching it into viewDidLoad where it's fetched.
-(void)viewDidLoad{
companies = [NSMutableArray array];
oldCompanies = [[NSUserDefaults standardUserDefaults] arrayForKey:#"companyData"];
if( companies )
{
for( NSData *data in oldCompanies )
{
companyObj = (Company*) [NSKeyedUnarchiver unarchiveObjectWithData:data];
[companies addObject:companyObj];
}
}
}
But outside viewDidLoad, whenever I try to access the data, the array "oldCompanies" as well as "companies" are shown "nil".
EDIT:
I'm encoding my Company object in a class which subclasses NSCoding like shown below but not allocating or retaining the properties anywhere. Can this be the catch?
- (void)encodeWithCoder:(NSCoder *)encoder
{
//Encode properties, other class variables, etc
[encoder encodeObject:self.companyId forKey:#"id"];
[encoder encodeObject:self.companyTitle forKey:#"title"];
[encoder encodeObject:self.companyImage forKey:#"image"];
}
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if( self != nil )
{
//decode properties, other class vars
self.companyId = [decoder decodeObjectForKey:#"id"];
self.companyTitle = [decoder decodeObjectForKey:#"title"];
self.companyImage = [decoder decodeObjectForKey:#"image"];
}
return self;
}
Can anybody please help?
Thanx in advance.
+array creates an autoreleased array - if you want to take ownership of it per the memory management rules then you need to retain it:
[companies retain];
Or create it so that it isn't autoreleased:
companies = [[NSMutableArray alloc] init];
Or, better, let declared properties do that for you:
// interface, property declaration:
#property(retain, readwrite) NSMutableArray *companies;
// implementation:
#synthesize companies;
// ... using it:
self.companies = [NSMutableArray array];
You are not retaining the array, when you dont do an alloc, or a retain when instatiating an object you get an autoreleased object, in your example companies is autoreleased and is why you cant access it anymore at a later point you should either alloc it
[[NSMutableArray alloc] init] or retain it [NSMutableArray array] retain]...either way refer to memory managment guide to learn about objective-c memory managment memory managment ref
Are you trying to access the data before the view is loaded?
Objective-C doesn't reset your pointers for you. If the array isn't "persisted", then the pointer will point to garbage.

Singleton shared data source in Objective-C

Hey folks - I'm writing a pretty simple iPhone application. The data comes from a plist file (NSDictionary basically), that I'm trying to load into a singleton class and use across my various view controllers to access the data.
Here's the implementation for my singleton (heavily modeled after this thread)
#implementation SearchData
#synthesize searchDict;
#synthesize searchArray;
- (id)init {
if (self = [super init]) {
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"searches.plist"];
searchDict = [NSDictionary dictionaryWithContentsOfFile:finalPath];
searchArray = [searchDict allKeys];
}
return self;
}
- (void)dealloc {
[searchDict release];
[searchArray release];
[super dealloc];
}
static SearchData *sharedSingleton = NULL;
+ (SearchData *)sharedSearchData {
#synchronized(self) {
if (sharedSingleton == NULL)
sharedSingleton = [[self alloc] init];
}
return(sharedSingleton);
}
#end
So whenever I try to access the searchDict or searchArray properties elsewhere in my application (like a TableView delegate) like so:
[[[SearchData sharedSearchData] searchArray] objectAtIndex:indexPath.row]
I get an exception stating *** -[NSCFSet objectAtIndex:]: unrecognized selector sent to instance 0x5551f0
I'm not really sure why the objectAtIndex message is being sent to an NSCFSet object, I feel like my singleton is implemented wrong or something. I also tried a more complex singleton implementation like the one recommended by apple in the aforementioned thread and had the same problem. Thanks for any insight you can provide.
In your -init method you are directly accessing your instance variables and you are not retaining them. They're getting deallocated and their memory is being used up by other objects later on in your application's lifetime.
Either retain your objects that you're creating there or use the non-convenience methods to generate them.
searchDict = [[NSDictionary alloc] initWithContentsOfFile:finalPath];
searchArray = [[searchDict allKeys] retain];
Whenever you assign synthesized variables, do it through 'self', so:
- (id)init {
if (self = [super init]) {
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"searches.plist"];
self.searchDict = [NSDictionary dictionaryWithContentsOfFile:finalPath];
self.searchArray = [searchDict allKeys];
}
return self;
}
Also make sure you've set up those variables to be 'retain'ed in the header file.
Hi, Can you tell me what is the advantage, when we assign synthesized variables through 'self'? Thank you shiva
the values are set through the setter; it releases the previous value and retains the one you assign.