2010/08/25

Notes on Memory Management in 
Objective-C - FMM(Frequently made mistakes)

To understand memory management is one thing (if it's no THE thing )you will be happy to know if you are or aim to be developer.
Cases like: developing a big project in mobile devices like the iPhone and then you realize you are running out of memory or  writing a simple app that suddenly crashes!
These might take hours of debugging to solve and I remember myself not so long time ago thinking - I wouldn't have this problem is I knew memory management rules well from the beginning -.

Everything remains in the object's retainCount and its ownership. This is what I am reviewing here.

Initializing Objects:
You can initialize an object using a alloc init methods, like:

NSArray *myArray = [[NSArray alloc] initWithObjects:@"Obj0", @"Obj1", @"Obj2", nil];
... do something with myArray
[myArray release];

or you can do it by using class methods or many convenience methods to create the object you want (without using alloc init).


NSArray *myArray = [NSArray arrayWithObjects:@"Obj0", @"Obj1", @"Obj2", nil];
... do something with myArray


So, what is the difference?
When you use alloc/init to create an object you own it. Owning and object or having its ownership implies that you are responsible of releasing it. When using init method the object is initialized and its retainCount is set to 1 and the system won't release it for you.
An object can be owned by one or more objects, implying that each of these owners must release it.

When you use one of many convenience methods like -arrayWithObjects: to create you object, you don't own that object. Why? Initialization might be the same, but the difference is that object will be added to an auto-release pool. Hence when the pool gets drained your object will be released also. (Actually, objects are often released earlier when they are not needed anymore)

That was the super easy part.  Now let's see how to pass an object we created to other objects' property.

Retain, assign or copy?

Consider the following code:
@interface UITextView : UIScrollView{



...
}

@property(nonatomic,assign) id delegate;;
@property(nonatomic,copy) NSString *text; 
@property(nonatomic,retain) UIFont *font;

@property (readwrite, retain) UIView *inputView;  


This is part of UITextView class in iOS SDK. Suppose we have an instance textView.


UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];

First, what everybody knows:


  • Retain: the passed object will be retained (its retainCount will increase generally by 1). Here,  textView will own the objects passed to font and inputView property.

  • Assign: the passed object will not be retained, (This is the default). If you don't set explicitly "retain" or "copy" then "assign" attribute will be assumed.

  • Copy: the passed object will be copied (generally a deep copy) Hence textView will own the object passed through text property. But the passed object and the retained object would be the same. When you have a deep copy it does not matter how you change the object from outside textView, it won't have effect, because it not same object as the one stored by text property. It's a mere copy.

Now we are fine with the theory, consider the following examples:

//A
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
[self.view addSubview:textView];
[textView release];
//B
NSString *myText2 = [[NSString alloc] initWithContentsOfFile:@"myTextFile.txt"
encoding:NSUTF8StringEncoding
  error:nil];
textView.text = myText2;
[myText2 release];
//C
textView.text = [[NSString alloc] initWithContentsOfFile:@"myTextFile.txt"
encoding:NSUTF8StringEncoding
  error:nil];
//D 
for (int i = 0; i < 100; i++)
textView.text = [[NSString alloc] initWithContentsOfFile:@"myTextFile.txt"
encoding:NSUTF8StringEncoding
  error:nil];
//E
UIView *keyboard = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 216)];
textView.inputView = keyboard;
[keyboard setBackgroundColor:[UIColor redColor]];

[keyboard release];
//F
textView.inputView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 216)];
//G
textView.inputView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 216)] autorelease];
//H
textView.text = [NSString stringWithContentsOfFile:@"myTextFile.txt"
  encoding:NSUTF8StringEncoding
error:nil];
//I
for (int i = 0; i < 100; i++)
textView.text = [NSString stringWithContentsOfFile:@"myTextFile.txt"
  encoding:NSUTF8StringEncoding
error:nil];

//J
MyDelegate *myDelegate = [[MyDelegate alloc] initWithSomething];
textView.delegate = myDelegate;
[myDelegate release];
//K
MyDelegate *myDelegate = [[MyDelegate alloc] initWithSomething];
textView.delegate = myDelegate;

//L
textView.delegate =  [[MyDelegate alloc] initWithSomething];


Some of these are very silly, a couple of them is just to show memory leaks in Instruments, but I see very often people write code like this in some programming boards and they are very illustrative.

A is just the initialization of our instance. This is not about properties.

B, C, D are samples regarding "copy" property attribute. This is the case of text property.

B is correct. Allocate and initialize myText2 (retainCount is 1), is then passed through text property then is copied, and since needed no more it's released. No leaks, we are fine.

C is wrong. You might be tempted to shorten your B code like C but this will cause memory leaks.
Reason: When you allocate and initialize your object it will have retainCount 1 and right after that it is passed through text property then is copied and you don't have a reference to release!.  This will be be more clearer in D.

D is just for illustrative purposes, you surely never want to do this. I did this just to make the leak very obvious in Instruments.
This is what I got in Instruments: pretty bad huh? Got 5 MB leaking (since my file size is 5KB)

Fig 1: Instruments showing leaked memory.


E and F are samples regarding "retain" property attribute. This is the case of inputView property.

E is correct, is basically like B, just this time keyboard is not copied, is the same object with it's retainCount increased. The big difference with copy is that if you change keyboard after you pass it to  textView.inputView it will do have effect on textView 's input view because is the same object.

F is wrong, is very similar to C, just this time the object you created using alloc and initWithFrame: methods is the same as the one stored through inputView property. Hence you have a reference and could solve this leak later (by doing [textView.inputView release];) but it will be ugly to my taste.

G is correct, this is what you want if you want to shorten E or correct F. I would use E rather than G.

H and I are samples regarding retain/copy but when using not owned objects. (object that will be autoreleased by an auto-release pool)

H is fine. In C, the bug was that object created just before passing it thought text property would not be released, but using the convenience method: -stringWithContentsOfFile:encoding:errorinstead of initWithContentsOfFile:encoding:errorthe problem is solved.
Why? when using those convenience methods the object returned is automatically polled in the most recent autorelease pool.
How do I recognize a convenience method? the rule is simple: if the word "init" is present in the method name (signature) then you will have the ownership of the object, meaning that you have to release it by yourself. Otherwise, is a convenience method, you will not have the ownership of the object because it will be added to an autorelease pool. You don't need to release it.

I is just to show that using this approach won't leak memory as D
This is what I got in Instruments:

Fig 2: Instruments showing allocation of first 100 text objects (starting the app).
Fig 3: Instruments showing released objects (app is already started).


Since I am allocating 100 times the same object then Allocations instrument shows a big amount of memory allocated at the beginning (shown in Fig 2). But since that memory is owned by the system, is drained when is not needed anymore (when the app finished loading), as shown in Fig 3

J,K and L are to show "assign" property attribute.
Assign behaves like retain in the sense is the same object (not a copy) is stored. The difference is that the object passed is not retained.

J is not wrong from the memory management point of view, but is not what you want because textView property will not retain myDelegate object. Hence releasing myDelegate right after setting the delegate through inputView property is meaningless, because the object will be deallocated.


K is what you want to do instead of J. This is correct.

L is the shortened version of K, since delegate property attribute is assign, textView will not retain myDelegate. Hence allocating and initializing and object and passing it is not a problem like in C or F.

So why assign this useful? it depends on your needs actually. The most famous usage is a delegate.
If object X retain an object Y that retains X, both objects will never be released, this is also a memory problem. you create objects that cannot be released. In this case assign becomes useful.

With this I've finished this quite long post. I hope it helped a little bit someone.
Actually I wanted to write also about how to write your own classes with properties, how to properly initialize and release them, but this post is too long already. A new post is better ;)



TODO:
Designing/Making/Using your own classes with properties

in UIViewController/ NSWindowController
in loadView/viewDidLoad
address problems like
NSString *string = [[NSString alloc] init];
string = [otherString stringByAppendingString:@"asd"];

NSString *string = [NSString string];
string = [otherString stringByAppendingString:@"asd"];

NSString *string = [otherString stringByAppendingString:@"asd"];

view didUnload:
self. retainProperty = nil;
[retainProperty release];
assignProperty = nil

in dealloc
[retainProperty release];
assignProperty = nil;
[copyProperty release];



0 comments :