这里打算写一些我对Iphone开发内存管理的理解。是建立在读者对Objective C的内存管理已经有一定理解的基础上,内容将包括实践准则,autorelease原理剖析,内存泄露的调试等。如果您对Objective C内存管理不太了解,建议先读一下Vince Yuan的这个教程。
在进行IPhone开发时, 内存管理是个相对比较难以理解的东西。Objective-C 使用了一种介于C#和C++之间的内存管理机制。C#是基于Mark-sweep的GC, C++基本上是程序员自己负责分配和释放。 Ojbective-C, 是程序员来负责Mark(通过Release, retain, alloc)计数, 系统来进行分配和释放。 下面是一些准则, 可以避免内存泄露和使用已释放的内存造成程序crash。
1)你必须为你自己创建的Object负责。 包括alloc, newObject, mutableCopy 等, 或者是你调用过retain的object。 对于这些Object, 你必须调用release 或autorelease。
2)对于其它函数返回的Object, 你不能release它, 原则上应该由被调用的函数来负责。e.g. NSString str = [NSString stringWithFormat:]. 你自己写的函数也最好遵循这个准则,谁申请,谁释放,而不要让调用者来释放。
3)如果你要在某个对象的property里存储另外一个对象, 你必须retain 或者copy 它, 避免它被别人release。
@property(retain)。。 这里的retain关键字会自动做这个事情。如果你自己声明set方 法就要手动做这个事情。
4)关于IPhone内存管理的其它10个Tips。 其中比较有用的包括尽量避免使用Autorelease,以及[UIImage imageNamed:],打开NSZombieEnabled.
5) weak reference. 如下图, Document 类中Page property 指向Page类的对象, page类中Parent property指向Document, 这样如果互相之间的retain count都是1的话,那么这两个object永远不会被Dealloc。 解决办法是把page.parent变为weak reference,也就是说, page.parent并不retain Document。 UITableView.datasource, notification observers, delegates, outline view items 等都是week reference。
如果你能够真正的理解autorelease,那么你才是理解了Objective c的内存管理。Autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的Autorelease pool中,当该pool被释放时,该pool中的所有Object会被调用Release。
[1]在Iphone项目中,大家会看到一个默认的Autorelease pool,程序开始时创建,程序退出时销毁,按照对Autorelease的理解,岂不是所有autorelease pool里的对象在程序退出时才release, 这样跟内存泄露有什么区别?
答案是,对于每一个Runloop, 系统会隐式创建一个Autorelease pool,这样所有的release pool会构成一个象CallStack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶的Autorelease pool会被销毁,这样这个pool里的每个Object会被release。
那什么是一个Runloop呢? 一个UI事件,Timer call, delegate call, 都会是一个新的Runloop。例子如下:
- NSString* globalObject;
- - (void)applicationDidFinishLaunching:(UIApplication *)application
- {
- globalObject = [[NSString alloc] initWithFormat:@"Test"];
- NSLog(@"Retain count after create: %d", [globalObject retainCount]);
- [globalObject retain];
- NSLog(@"Retain count after retain: %d", [globalObject retainCount]);
- }
- - (void)applicationWillTerminate:(UIApplication *)application
- {
- NSLog(@"Retain count after Button click runloop finished: %d", [globalObject retainCount]);
-
- }
- -(IBAction)onButtonClicked
- {
- [globalObject autorelease];
- NSLog(@"Retain count after autorelease: %d", [globalObject retainCount]);
-
- }
[2]为什么需要Auto release ?
2.1)很多C/C++转过来的程序员会说,这个auto release有什么好,象C/C++那样,自己申请,自己释放,完全可控不好么, 这个auto relase 完全不可控,你都不知到它什么时候会被真正的release。我的理解它有一个作用就是可以做到每个函数对自己申请的对象负责,自己申请,自己释放,该函数的调用者不需要关心它内部申请对象的管理。 在下面这个例子中,Func1的调用者不需要再去关心obj的释放。
- ClassA *Func1()
- {
- ClassA *obj = [[[ClassA alloc]init]autorelease];
- return obj;
- }
实际上对于 [NSString stringWithFormat:] 这类构造函数返回的对象都是autorelease的。
2.2) autorelease pool来避免频繁申请/释放内存(就是pool的作用了)。这个应该是相对比较好理解的。
总结:1)一定要注意Autorelease pool的生存周期,理解Runloop,避免在对象被释放后使用。
2)[NSString stringWithFormat:]这类函数返回的对象是不需要再自己release的,它已经被autorelease了, 如果你想把它当一个全局对象使用,那必须自己再retain, 释放时再release。