PDA

View Full Version : [iPhone] Memory Management with Collections




Enuratique
Jun 23, 2008, 02:23 PM
Hi all,

Got another n00b question for you... You've been great about answering my questions so far! Thanks!

I'm wondering if I need to explicitly call release on arrays returned from methods in the collection classes. For example, I'd like to know if a key already exists for an instance of NSMutableDictionary. In .NET, the Dictionary class has a handy KeyExists(string) method which unfortunately NSMutableDictionary does not have. What I can do, however, is iterate over the collection returned by NSMutableDictionary's allKeys method. The documentation for this method states "A new array containing the receiverís keys, or an empty array if the receiver has no entries." [emphasis added by me].

My question is whether or not I need to explicitly call release on the array returned by this once I'm done searching for a key in the array. EG:

- (BOOL)keyExists:(id)key {
BOOL keyExists = NO;
NSArray *keys = [dictionary allKeys];
keyExists = [keys containsObject:key];
[keys release];
return keyExists;
}

First question, will containsObject find it if they're not the exact same reference? And will calling release cause me a whole bushel of problems?

Thanks again guys!



Sbrocket
Jun 23, 2008, 03:01 PM
First, the question you didn't ask. What you actually want to do (or rather, what you did works but this is simpler) is the following:


if ([myDictionary valueForKey:someKey]) {
// Do what you want to in the case where the key exists here
}


This is because valueForKey: will return nil if there is no object associated with the given key, at which point that condition will evaluate to false.

Second, the memory management question:


- (BOOL)keyExists:(id)key {
BOOL keyExists = NO;
NSArray *keys = [dictionary allKeys];
keyExists = [keys containsObject:key];
[keys release];
return keyExists;
}


In this block, you have a net retain count change of -1 -- that's not good (unless you have some specific purpose, like in init or dealloc, and know what you're doing). You'll probably get a some sort of error (or worse, an error further down the line that's harder to find the source of) here as [dictionary allKeys] can be assumed to return an auto-released object.

The general rule is this: all objects that you take ownership of with either a retain statement or through a init or copy, you need to subsequently release (or auto-release, if needed) ownership of when you're done using it. As you don't take ownership of the NSArray keys here since there is no retain and no method involving with init or copy, you didn't take ownership of it and you should not release it.

Or you get errors. Errors are bad. This is what you want to do:

- (BOOL)keyExists:(id)key {
BOOL keyExists = NO;
NSArray *keys = [dictionary allKeys];
keyExists = [keys containsObject:key];
return keyExists;
}

Much much better. :p

The following is a really good read on Memory Management:
http://www.stepwise.com/Articles/Technical/MemoryManagement.html

Also, I'd look up NSZombieEnabled for debugging your memory management problems. Its really a godsend.

Enuratique
Jun 23, 2008, 03:35 PM
The following is a really good read on Memory Management:
http://www.stepwise.com/Articles/Technical/MemoryManagement.html

Also, I'd look up NSZombieEnabled for debugging your memory management problems. Its really a godsend.

Sbrocket - you rock! What I'm ultimately trying to accomplish is:

MyObject *current = root;
for (int key = 0; key < [keys length]; key++) {
NSNumber someKey = [NSNumber initWithInt: key];
if (![current.Dictionary valueForKey:someKey]) {
MyObject *myObject = [[MyObject alloc] init];
[current.Dictionary setObject:myObject forKey:someKey];
[myObject release];
}
current = [current.Dictionary valueForKey: someKey];
}


Now this arises two more questions... I'm trying to store words (strings) into an array in a dictionary using the word length as the key. Unfortunately, keys need to observe the NSCopying protocol - and I'm assuming ints don't get compiled into NSNumber objects (in .NET, an int is simply compiled into a System.Int32 which is more or less an object, and not a value type). Is there a more efficient way to do this? Also, in following some of the code samples online where they initialize a ViewController in a viewLoaded override, after they set what they allocated to a property which is synthesized with retain, they call release on the local temp variable. The documentation for setObject says it sends a retain message to the object, so I'm assuming I'm safe in calling release on my local object. I'm also assuming that if I DON'T do this, then the retain count will never decrement to 0.

Thanks for the link - I'll definitely check it out. The thought of simply checking the return value from attempting to retrieve a value for a key did not dawn on me. This is partially because I'm trying to port over a utility I wrote in C# / .NET and in .NET, trying to access an object for which a key does not exist will thrown an exception. I'm just trying to get the dang thing implemented at this point - I haven't even gotten it to a point where it will run, so just finding out whether these things will cause a crash through my own testing is infeasible at the moment. My next task, once implemented, will figure out how to find memory leaks using Instruments.

Fortunately, back in college they made us take a course in Squeak (a Smalltalk variant) so atleast I can follow along with Objective-C without too many problems.

So far I'm liking developing with XCode and Objective-C... There's just a steep learning curve when I've been used to the Microsoft way of doing things for lo these past 4 years.

Sbrocket
Jun 23, 2008, 03:58 PM
MyObject *current = root;
for (int key = 0; key < [keys length]; key++) {
NSNumber someKey = [NSNumber initWithInt: key];
if (![current.Dictionary valueForKey:someKey]) {
MyObject *myObject = [[MyObject alloc] init];
[current.Dictionary setObject:myObject forKey:someKey];
[myObject release];
}
current = [current.Dictionary valueForKey: someKey];
}



First of, a couple things that come to mind as I'm reading through this:

What is root? Some instance variable of type MyObject or something?
You probably want to use [NSNumber numberWithInt:key] instead to get an autoreleased instance of NSNumber, rather than having to release it yourself later. Its just a convenience operator.
Cocoa good practices nitpick - instance variables should begin with a lowercase letter, so current.dictionary not current.Dictionary


Now this arises two more questions... I'm trying to store words (strings) into an array in a dictionary using the word length as the key.

I'm not quite sure what you're doing here in the grand scheme of things, so I'm going to assume for the sake of answering questions that your overall design is correct for what you want to do for the most part. One thing, though - is it a guaranteed assertion that your keys (i.e. your word lengths) will be unique for every string you need to store? Keys (though not values) need to be unique or the use behind a dictionary is lost. Non-unique keys would result in unwanted behavior.

Unfortunately, keys need to observe the NSCopying protocol - and I'm assuming ints don't get compiled into NSNumber objects (in .NET, an int is simply compiled into a System.Int32 which is more or less an object, and not a value type). Is there a more efficient way to do this?

You can technically use any object as a key. The "typical" thing to do is to use an NSString, but that's by no means necessary. You're right about the need to create an object wrapper (NSNumber in this case) for the int though. None of that stuff is done automatically, unfortunately, and you'd get compiler warnings if you tried to use an int directly for the key. You'll find object wrappers such as NSNumbers needed in a lot of cases where it seems like it would "just be easier" if it was done automatically, but it's something to get used to.

Also, in following some of the code samples online where they initialize a ViewController in a viewLoaded override, after they set what they allocated to a property which is synthesized with retain, they call release on the local temp variable. The documentation for setObject says it sends a retain message to the object, so I'm assuming I'm safe in calling release on my local object. I'm also assuming that if I DON'T do this, then the retain count will never decrement to 0.

I'm not sure what sample code you're talking about without a specific link, but what you're doing in the code block above by releasing the ownership of the created MyObject instance after adding it to the dictionary is 100% correct so I think you've got the idea.

My next task, once implemented, will figure out how to find memory leaks using Instruments.

GDB (the debugger) and its associated tools as well as Instruments are really great once you learn how to use them well. You'll find new tricks to make your debugging easier all the time. :cool:

Lemme know if you've got any other questions.

Enuratique
Jun 23, 2008, 04:46 PM
First of, a couple things that come to mind as I'm reading through this:

What is root? Some instance variable of type MyObject or something?
You probably want to use [NSNumber numberWithInt:key] instead to get an autoreleased instance of NSNumber, rather than having to release it yourself later. Its just a convenience operator.
Cocoa good practices nitpick - instance variables should begin with a lowercase letter, so current.dictionary not current.Dictionary


I'm not quite sure what you're doing here in the grand scheme of things, so I'm going to assume for the sake of answering questions that your overall design is correct for what you want to do for the most part. One thing, though - is it a guaranteed assertion that your keys (i.e. your word lengths) will be unique for every string you need to store? Keys (though not values) need to be unique or the use behind a dictionary is lost. Non-unique keys would result in unwanted behavior.


Yes, the overall design I believe is sound seeing as I have a working version in .NET... LOL about the nitpick, in .NET your properties should be capitalized - don't worry, in my code it's lowercase, I just deleted your prefix of "my" when replying. Essentially, I'm trying to build a tree. Each TreeNode object in the tree simply has a Dictionary of TreeNodes representing the child nodes. I could have simply used a list for this, but without getting into too many details, for my needs the dictionary is a little bit more efficient. In my provided routine, I'm trying to build up a Tree, one level at a time... Hope that clears things up a little bit without giving away too much of what the intent of this utility is.

Also, yes, what I'm going to bind to in my UITableView is a Dictionary whose keys are word lengths, and the object being retrieved by that key will be a list of words whose length is the key. So yes, I don't think I have to worry too much about having unique keys, I basically want to index my TableView by word length, so that when I retrieve the NSArray of NSStrings at key 2, I know I'll have all the two letter words I'm searching for.

As for the example, if you download the UIShowcase or the UITableSuite code samples, you'll see what I mean about releasing locally allocated Views/ViewControllers.

Good to know I'm on the right track! Thanks again for you help.

Sbrocket
Jun 23, 2008, 04:49 PM
Heh, that's funny. My current project actually involves building a binary expression tree to solve some stuff and I've got a TreeNode class too, to represent each node (and using an NSDictionary to build each parent<-->children relationship). Go figure.

Good luck.

Enuratique
Jun 23, 2008, 05:40 PM
Heh, that's funny. My current project actually involves building a binary expression tree to solve some stuff and I've got a TreeNode class too, to represent each node (and using an NSDictionary to build each parent<-->children relationship). Go figure.

Good luck.

Thanks, you too. Writing an iPhone version of Mathematica?

Sbrocket
Jun 24, 2008, 01:17 AM
Thanks, you too. Writing an iPhone version of Mathematica?

Hah, no, not something quite that big. Its a conversion utility targeted at engineers and students since we're always doing complex conversions between different unit systems and whatnot that get confusing when done by hand.

I.E. This:
http://dl.getdropbox.com/u/417/UIPhoto18.png
http://dl.getdropbox.com/u/417/UIPhoto19.png

Funny thing is it was originally meant to just be something for me + a project to learn Objective-C and CocoaTouch, but then I just ran with the idea. :D

HiRez
Jun 24, 2008, 03:00 AM
Funny thing is it was originally meant to just be something for me + a project to learn Objective-C and CocoaTouch, but then I just ran with the idea. :DThose are the best kind of projects and often the most successful ones (or at least the ones that actually get finished ;) ).