版权声明
此文版权归作者Vince Yuan (vince.yuan#gmail.com)
所有。欢迎非营利性转载,转载时必须包含原始链接 http://vinceyuan.cnblogs.com/
,且必须包含此版权声明的完整内容。
前言
初学objectice-C
的朋友都有一个困惑,总觉得对objective-C
的内存管理机制琢磨不透,程序经常内存泄漏或莫名其妙的崩溃。我在这里总结了自己对objective-C
内存管理机制的研究成果和经验,写了这么一个由浅入深的教程。希望对大家有所帮助,也欢迎大家一起探讨。
此文涉及的内存管理是针对于继承于NSObject
的Class
。
一
基本原理
Objective-C
的内存管理机制与.Net/Java
那种全自动的垃圾回收机制是不同的,它本质上还是C
语言中的手动管理方式,只不过稍微加了一些自动方法。
1 Objective-C
的对象生成于堆之上,生成之后,需要一个指针来指向它。
ClassA *obj1 = [[ClassA alloc] init];
2 Objective-C
的对象在使用完成之后不会自动销毁,需要执行dealloc
来释放空间(销毁),否则内存泄露。
[obj1 dealloc];
这带来了一个问题。下面代码中obj2
是否需要调用dealloc
?
ClassA *obj1 = [[ClassA alloc] init];
ClassA *obj2 = obj1;
[obj1 hello]; //
输出hello
[obj1 dealloc];
[obj2 hello]; //
能够执行这一行和下一行吗?
[obj2 dealloc];
不能,因为obj1
和obj2
只是指针,它们指向同一个对象,[obj1 dealloc]
已经销毁这个对象了,不能再调用[obj2 hello]
和[obj2 dealloc]
。obj2
实际上是个无效指针。
如何避免无效指针?请看下一条。
3 Objective-C
采用了引用计数(ref count
或者retain count)
。对象的内部保存一个数字,表示被引用的次数。例如,某个对象被两个指针所指向(引用)那么它的retain count
为2
。需要销毁对象的时候,不直接调用dealloc
,而是调用release
。release
会让retain count
减1
,只有retain count
等于0
,系统才会调用dealloc
真正销毁这个对象。
ClassA *obj1 = [[ClassA alloc] init]; //
对象生成时,retain count = 1
[obj1 release]; //release
使retain count
减1
,retain count = 0
,dealloc
自动被调用,
对象被销毁
我们回头看看刚刚那个无效指针的问题,把dealloc
改成release
解决了吗?
ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1
ClassA *obj2 = obj1; //retain count = 1
[obj1 hello]; //
输出hello
[obj1 release]; //retain count = 0
,对象被销毁
[obj2 hello];
[obj2 release];
[obj1 release]
之后,obj2
依然是个无效指针。问题依然没有解决。解决方法见下一条。
4 Objective-C
指针赋值时,retain count
不会自动增加,需要手动retain
。
ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1
ClassA *obj2 = obj1; //retain count = 1
[obj2 retain]; //retain count = 2
[obj1 hello]; //
输出hello
[obj1 release]; //retain count = 2 �C 1 = 1
[obj2 hello]; //
输出hello
[obj2 release]; //retain count = 0
,对象被销毁
问题解决!注意,如果没有调用[obj2 release]
,这个对象的retain count
始终为1
,不会被销毁,内存泄露。(1-4
可以参考附件中的示例程序memman-no-pool.m)
这样的确不会内存泄露,但似乎有点麻烦,有没有简单点的方法?见下一条。
5 Objective-C
中引入了autorelease pool
(自动释放对象池),在遵守一些规则的情况下,可以自动释放对象。(autorelease pool
依然不是.Net/Java
那种全自动的垃圾回收机制)
5.1
新生成的对象,只要调用autorelease
就行了,无需再调用release
!
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1
但无需调用release
5.2
对于存在指针赋值的情况,代码与前面类似。
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1
ClassA *obj2 = obj1; //retain count = 1
[obj2 retain]; //retain count = 2
[obj1 hello]; //
输出hello
//
对于obj1
,无需调用(实际上不能调用)release
[obj2 hello]; //
输出hello
[obj2 release]; //retain count = 2-1 = 1
细心的读者肯定能发现这个对象没有被销毁,何时销毁呢?谁去销毁它?(可以参考附件中的示例程序memman-with-pool.m
)请看下一条。
6 autorelease pool
原理剖析。(其实很简单的,一定要坚持看下去,否则还是不能理解Objective-C
的内存管理机制。)
6.1 autorelease pool
不是天生的,需要手动创立。只不过在新建一个iphone
项目时,xcode
会自动帮你写好。autorelease pool
的真名是NSAutoreleasePool
。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
6.2 NSAutoreleasePool
内部包含一个数组(NSMutableArray
),用来保存声明为autorelease
的所有对象。如果一个对象声明为autorelease
,系统所做的工作就是把这个对象加入到这个数组中去。
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1
,把此对象加入autorelease pool
中
6.3 NSAutoreleasePool
自身在销毁的时候,会遍历一遍这个数组,release
数组中的每个成员。如果此时数组中成员的retain count
为1
,那么release
之后,retain count
为0
,对象正式被销毁。如果此时数组中成员的retain count
大于1
,那么release
之后,retain count
大于0
,此对象依然没有被销毁,内存泄露。
6.4
默认只有一个autorelease pool
,通常类似于下面这个例子。
int main (int argc, const char *argv[])
{
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
// do something
[pool release];
return (0);
} // main
所有标记为autorelease的对象都只有在这个pool销毁时才被销毁。如果你有大量的对象标记为autorelease
,这显然不能很好的利用内存,在iphone
这种内存受限的程序中是很容易造成内存不足的。例如:
int main (int argc, const char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int i, j;
for (i = 0; i < 100; i++ )
{
for (j = 0; j < 100000; j++ )
[NSString stringWithFormat:@"1234567890"];//
产生的对象是autorelease
的。
}
[pool release];
return (0);
} // main
(可以参考附件中的示例程序memman-many-objs-one-pool.m
,运行时通过监控工具可以发现使用的内存在急剧增加,直到pool
销毁时才被释放)你需要考虑下一条。
7 Objective-C
程序中可以嵌套创建多个autorelease pool
。在需要大量创建局部变量的时候,可以创建内嵌的autorelease pool
来及时释放内存。(感谢网友hhyytt和neogui的提醒,某些情况下,系统会自动创建autorelease pool, 请参见第四章)
int main (int argc, const char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int i, j;
for (i = 0; i < 100; i++ )
{
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
for (j = 0; j < 100000; j++ )
[NSString stringWithFormat:@"1234567890"];//
产生的对象是autorelease
的。
[loopPool release];
}
[pool release];
return (0);
} // main
(可以参考附件中的示例程序memman-many-objs-many-pools.m
,占用内存的变化极小)
iPhone/Mac Objective-C内存管理教程和原理剖析(二)口诀与范式
二
口诀与范式
1
口诀。
1.1
谁创建,谁释放(类似于“谁污染,谁治理”)。如果你通过
alloc
、
new
或
copy
来创建一个对象,那么你必须调用
release
或
autorelease
。换句话说,不是你创建的,就不用你去释放。
例如,你在一个函数中
alloc
生成了一个对象,且这个对象只在这个函数中被使用,那么你必须在这个函数中调用
release
或
autorelease
。如果你在一个
class
的某个方法中
alloc
一个成员对象,且没有调用
autorelease
,那么你需要在这个类的
dealloc
方法中调用
release
;如果调用了
autorelease
,那么在
dealloc
方法中什么都不需要做。
1.2
除了
alloc
、
new
或
copy
之外的方法创建的对象都被声明了
autorelease
。
1.3
谁
retain
,谁
release
。只要你调用了
retain
,无论这个对象是如何生成的,你都要调用
release
。有时候你的代码中明明没有
retain
,可是系统会在默认实现中加入
retain
。不知道为什么苹果公司的文档没有强调这个非常重要的一点,请参考范式
2.7
和第三章。
2
范式。
范式就是模板,就是依葫芦画瓢。由于不同人有不同的理解和习惯,我总结的范式不一定适合所有人,但我能保证照着这样做不会出问题。
2.1
创建一个对象。
ClassA *obj1 = [[ClassA alloc] init];
2.2
创建一个
autorelease
的对象。
ClassA *obj1 = [[[ClassA alloc] init] autorelease];
2.3
Release
一个对象后,立即把指针清空。(顺便说一句,
release
一个空指针是合法的,但不会发生任何事情)
[obj1 release];
obj1 = nil;
2.4
指针赋值给另一个指针。
ClassA *obj2 = obj1;
[obj2 retain];
//do something
[obj2 release];
obj2 = nil;
2.5
在一个函数中创建并返回对象,需要把这个对象设置为
autorelease
ClassA *Func1()
{
ClassA *obj = [[[ClassA alloc]init]autorelease];
return obj;
}
2.6
在子类的
dealloc
方法中调用基类的
dealloc
方法
-(void) dealloc
{
…
[super dealloc];
}
2.7
在一个
class
中创建和使用
property
。
2.7.1
声明一个成员变量。
ClassB *objB;
2.7.2
声明
property
,加上
retain
参数。
@property (retain) ClassB* objB;
2.7.3
定义
property
。(
property
的默认实现请看第三章)
@synthesize objB;
2.7.4
除了
dealloc
方法以外,始终用
.
操作符的方式来调用
property
。
self.objB
或者
objA.objB
2.7.5
在
dealloc
方法中
release
这个成员变量。
[objB release];
示例代码如下(详细代码请参考附件中的
memman-property.m
,你需要特别留意对象是在何时被销毁的。):
@interface ClassA : NSObject
{
ClassB* objB;
}
@property (retain) ClassB* objB;
@end
@implementation ClassA
@synthesize objB;
-(void) dealloc
{
[objB release];
[super dealloc];
}
@end
2.7.6
给这个
property
赋值时,有手动
release
和
autorelease
两种方式。
void funcNoAutorelease()
{
ClassB *objB1 = [[ClassB alloc]init];
ClassA *objA = [[ClassA alloc]init];
objA.objB = objB1;
[objB1 release];
[objA release];
}
void funcAutorelease()
{
ClassB *objB1 = [[[ClassB alloc]init] autorelease];
ClassA *objA = [[[ClassA alloc]init] autorelease];
objA.objB = objB1;
}
示例代码文件链接:
http://files.cnblogs.com/VinceYuan/objective-c-memman.zip