前言
初学objectice-C的朋友都有一个困惑,总觉得对objective-C的内存管理机制琢磨不透,程序经常内存泄漏或莫名其妙的崩溃。我在这里总结了自己对objective-C内存管理机制的研究成果和经验,写了这么一个由浅入深的教程。希望对大家有所帮助,也欢迎大家一起探讨。
此文涉及的内存管理是针对于继承于NSObject的Class。
一
Objective-C的内存管理机制与.Net/Java那种全自动的垃圾回收机制是不同的,它本质上还是C语言中的手动管理方式,只不过稍微加了一些自动方法。
1
ClassA *obj1 = [[ClassA alloc] init];
2
[obj1 dealloc];
ClassA *obj1 = [[ClassA alloc] init];
ClassA *obj2 = obj1;
[obj1 hello]; //输出hello
[obj1 dealloc];
[obj2 hello]; //能够执行这一行和下一行吗?
[obj2 dealloc];
3
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];
4
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],这个对象的retaincount始终为1,不会被销毁,内存泄露。(1-4可以参考附件中的示例程序memman-no-pool.m)
这样的确不会内存泄露,但似乎有点麻烦,有没有简单点的方法?见下一条。
5
5.1
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retaincount = 1
5.2
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retaincount = 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
6.1
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
6.2
ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retaincount = 1,把此对象加入autorelease pool中
6.3
6.4
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++ )
{
}
[pool release];
return (0);
} // main
(可以参考附件中的示例程序memman-many-objs-one-pool.m,运行时通过监控工具可以发现使用的内存在急剧增加,直到pool销毁时才被释放)你需要考虑下一条。
7
int main (int argc, const char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int i, j;
for (i = 0; i < 100; i++ )
{
}
[pool release];
return (0);
} // main
(可以参考附件中的示例程序memman-many-objs-many-pools.m,占用内存的变化极小)
二
1
1.1
例如,你在一个函数中alloc生成了一个对象,且这个对象只在这个函数中被使用,那么你必须在这个函数中调用release或autorelease。如果你在一个class的某个方法中alloc一个成员对象,且没有调用autorelease,那么你需要在这个类的dealloc方法中调用release;如果调用了autorelease,那么在dealloc方法中什么都不需要做。
1.2
1.3
2
范式就是模板,就是依葫芦画瓢。由于不同人有不同的理解和习惯,我总结的范式不一定适合所有人,但我能保证照着这样做不会出问题。
2.1
ClassA *obj1= [[ClassA alloc] init];
2.2
ClassA *obj1= [[[ClassA alloc] init] autorelease];
2.3
[obj1 release];
obj1 = nil;
2.4
ClassA *obj2= obj1;
[obj2retain];
//dosomething
[obj2release];
obj2 =nil;
2.5
ClassA *Func1()
{
}
2.6
-(void) dealloc
{
}
2.7
2.7.1
ClassB*objB;
2.7.2
@property(retain) ClassB* objB;
2.7.3
@synthesizeobjB;
2.7.4
self.objB
2.7.5
[objBrelease];
示例代码如下(详细代码请参考附件中的memman-property.m,你需要特别留意对象是在何时被销毁的。):
@interface ClassA : NSObject
{
}
@property (retain) ClassB* objB;
@end
@implementation ClassA
@synthesize objB;
-(void) dealloc
{
}
@end
2.7.6
void funcNoAutorelease()
{
}
void funcAutorelease()
{
}
三
在这里解释一下@property (retain) ClassB*objB;和@synthesizeobjB;背后到底发生了什么(retain property的默认实现)。property实际上是getter和setter,针对有retain参数的property,背后的实现如下(请参考附件中的memman-getter-setter.m,你会发现,结果和memman-property.m一样):
@interface ClassA : NSObject
{
}
-(ClassB *) getObjB;
-(void) setObjB:(ClassB *)value;
@end
@implementation ClassA
-(ClassB*) getObjB
{
}
-(void) setObjB:(ClassB*) value
{
}
在setObjB中,如果新设定的值和原值不同的话,必须要把原值对象release一次,这样才能保证retaincount是正确的。
由于我们在class内部retain了一次(虽然是默认实现的),所以我们要在dealloc方法中release这个成员变量。
-(void) dealloc
{
}
四
在生成新的RunLoop的时候,系统会自动创建新的autorelease pool(非常感谢网友hhyytt和neogui的提醒)。注意,此处不同于xcode在新建项目时自动生成的代码中加入的autoreleasepool,xcode生成的代码可以被删除,但系统自动创建的新的autoreleasepool是无法删除的(对于无Garbage Collection的环境来说)。Objective-C没有给出实现代码,官方文档也没有说明,但我们可以通过小程序来证明。
在这个小程序中,我们先生成了一个autorelease pool,然后生成一个autorelease的ClassA的实例,再在一个新的runloop中生成一个autorelease的ClassB的对象(注意,我们并没有手动在新runloop中生成autoreleasepool)。精简的示例代码如下,详细代码请见附件中的memman-run-loop-with-pool.m。
int main(int argc,char**argv)
{
}
输出如下:
create anautorelasePool
create aninstance of ClassA and autorelease
create aninstance of ClassB and autorelease
ClassBdestroyed
releasingautorelasePool
ClassAdestroyed
autorelasePool is released
注意在我们销毁autorelease pool之前,ClassB的autorelease实例就已经被销毁了。
有人可能会说,这并不能说明新的run loop自动生成了一个新的autoreleasepool,说不定还只是用了老的autorelease pool,只不过后来drain了一次而已。我们可以在main函数中不生成autoreleasepool。精简的示例代码如下,详细代码请见附件中的memman-run-loop-without-pool.m。
int main(int argc,char**argv)
{
}
输出如下:
NoautorelasePool created
create aninstance of ClassA
create aninstance of ClassB and autorelease
ClassBdestroyed
Manuallyrelease the instance of ClassA
ClassAdestroyed
我们可以看出来,我们并没有创建任何autorelease pool,可是ClassB的实例依然被自动销毁了,这说明新的runloop自动创建了一个autorelease pool,这个pool在新的runloop结束的时候会销毁自己(并自动release所包含的对象)。
补充说明
在研究retaincount的时候,我不建议用NSString。因为在下面的语句中,
NSString*str1 = @”constant string”;
str1的retain count是个很大的数字。Objective-C对常量字符串做了特殊处理。
当然,如果你这样创建NSString,得到的retain count依然为1
NSString*str2 = [NSString stringWithFormat:@”123”];