尽管苹果在 iOS 5/ Mac OS X 10.7 开始导入ARC,利用 Xcode4.2 可使用该机能。ARC就是自动引用计数,是一项为Objective - C程序在编译时提供自动内存管理的功能。ARC可让你把注意力集中在你感兴趣的代码,对象图,和你的应用程序中的对象之间的关系,让你没必要再花费精力在retain和release操做上。objective-c
然而在一些老的项目中仍然须要使用手动去管理内存,防止内存泄露,并且学习一下手动管理对本身也有好处。原本打算本身写一篇,偶然发现已有一篇写的比较清晰明了的,方便初学者掌握关于内存方面知识。小程序
前言
初学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 – 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,占用内存的变化极小)数组
二 口诀与范式
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;
}xcode
三 @property (retain)和@synthesize的默认实现
在这里解释一下@property (retain) ClassB* objB;和@synthesize objB;背后到底发生了什么(retain property的默认实现)。property其实是getter和setter,针对有retain参数的property,背后的实现以下(请参考附件中的memman-getter-setter.m,你会发现,结果和memman-property.m同样):
@interface ClassA : NSObject
{
ClassB *objB;
}
-(ClassB *) getObjB;
-(void) setObjB:(ClassB *) value;
@end
@implementation ClassA
-(ClassB*) getObjB
{
return objB;
}
-(void) setObjB:(ClassB*) value
{
if (objB != value)
{
[objB release];
objB = [value retain];
}
}
在setObjB中,若是新设定的值和原值不一样的话,必需要把原值对象release一次,这样才能保*****/span>retain count是正确的。
因为咱们在class内部retain了一次(虽然是默认实现的),因此咱们要在dealloc方法中release这个成员变量。
-(void) dealloc
{
[objB release];
[super dealloc];
}iphone
四 系统自动建立新的autorelease pool
在生成新的Run Loop的时候,系统会自动建立新的autorelease pool(很是感谢网友hhyytt和neogui的提醒)。注意,此处不一样于xcode在新建项目时自动生成的代码中加入的autorelease pool,xcode生成的代码能够被删除,但系统自动建立的新的autorelease pool是没法删除的(对于无Garbage Collection的环境来讲)。Objective-C没有给出实现代码,官方文档也没有说明,但咱们能够经过小程序来证实。
在这个小程序中,咱们先生成了一个autorelease pool,而后生成一个autorelease的ClassA的实例,再在一个新的run loop中生成一个autorelease的ClassB的对象(注意,咱们并无手动在新run loop中生成autorelease pool)。精简的示例代码以下,详细代码请见附件中的memman-run-loop-with-pool.m。
int main(int argc, char**argv)
{
NSLog(@"create an autorelasePool\n");
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(@"create an instance of ClassA and autorelease\n");
ClassA *obj1 = [[[ClassA alloc] init] autorelease];
NSDate *now = [[NSDate alloc] init];
NSTimer *timer = [[NSTimer alloc] initWithFireDate:now
interval:0.0
target:obj1
selector:@selector(createClassB)
userInfo:nil
repeats:NO];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
[timer release];
[now release];
[runLoop run]; //在新loop中调用一函数,生成ClassB的autorelease实例
NSLog(@"releasing autorelasePool\n");
[pool release];
NSLog(@"autorelasePool is released\n");
return 0;
}
输出以下:
create an autorelasePool
create an instance of ClassA and autorelease
create an instance of ClassB and autorelease
ClassB destroyed
releasing autorelasePool
ClassA destroyed
autorelasePool is released
注意在咱们销毁autorelease pool以前,ClassB的autorelease实例就已经被销毁了。
有人可能会说,这并不能说明新的run loop自动生成了一个新的autorelease pool,说不定还只是用了老的autorelease pool,只不事后来drain了一次而已。咱们能够在main函数中不生成autorelease pool。精简的示例代码以下,详细代码请见附件中的memman-run-loop-without-pool.m。
int main(int argc, char**argv)
{
NSLog(@"No autorelasePool created\n");
NSLog(@"create an instance of ClassA\n");
ClassA *obj1 = [[ClassA alloc] init];
NSDate *now = [[NSDate alloc] init];
NSTimer *timer = [[NSTimer alloc] initWithFireDate:now
interval:0.0
target:obj1
selector:@selector(createClassB)
userInfo:nil
repeats:NO];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
[timer release];
[now release];
[runLoop run]; //在新loop中调用一函数,生成ClassB的autorelease实例
NSLog(@"Manually release the instance of ClassA\n");
[obj1 release];
return 0;
}
输出以下:
No autorelasePool created
create an instance of ClassA
create an instance of ClassB and autorelease
ClassB destroyed
Manually release the instance of ClassA
ClassA destroyed
咱们能够看出来,咱们并无建立任何autorelease pool,但是ClassB的实例依然被自动销毁了,这说明新的run loop自动建立了一个autorelease pool,这个pool在新的run loop结束的时候会销毁本身(并自动release所包含的对象)。
补充说明
在研究retain count的时候,我不建议用NSString。由于在下面的语句中,
NSString *str1 = @”constant string”;
str1的retain count是个很大的数字。Objective-C对常量字符串作了特殊处理。
固然,若是你这样建立NSString,获得的retain count依然为1
NSString *str2 = [NSString stringWithFormat:@”123”];函数
源码下载:http://files.cnblogs.com/VinceYuan/objective-c-memman.zip 工具
原文地址:http://vinceyuan.cnblogs.comoop