tags: OC 30 day
原文位置网址
Objective-C里的记忆体管理主要有两种:
Garbage Collection(缩写为GC)Reference Counting(缩写为RC)以车子来举例的话,你可以先暂时把这两种记忆体管理方式想像成GC是自排车,RC是手排车,一个会自动帮忙回收没要用的记忆体,一个则是自己得手动释放用过不要再用的记忆体。
RC的运作机制
RC的运作机制是当某个物件生成并初始化之后,它的初始retain count会设定成1(其实不一定,也是有例外)。执行该物件的”retain”方法会让该物件的retain count加1,而release方法会让retain count减1。当该物件的retain count降到0的时候,这个物件自动会呼叫dealloc方法把自己解决掉,然后把佔用的记忆体还回来。
也许你会觉得,都什么年代了为什么还得程式设计师自己用手动的方式来回收记忆体? 换个角度想,程式设计师为自己写的程式负责也是件好事,另外,也是最主要的原因就是有些环境就是根本不支援GC机制,所以只好用RC来处理。我相信想学Objective-C很多人都是为了想要开发iPhone App而来的,而iPhone正是那个不支援GC环境的其中之一。
又或许你会觉得这样一颗小物件是能佔多少记忆体。这种东西积沙成塔的,你借了记忆体来用却没还回去,久了可能就会造成”漏水”(memory leaking)的情况。为了避免App在执行的过程中莫名奇妙的地方当掉,只好乖乖的来了解一下关于记忆体管理的机制。
You only release or autorelease objects you own.
Mac OS X Developer Library
Memory Management Policy
什么是你拥用的物件? 当你用alloc、new或是copy开头的方法建立一个物件的话,程式就会向系统要一块记忆体来放这颗物件,而这颗物件就算在你头上。另外当你用对某个物件使用retain方法之后,那颗物件也算是你要负责的;一个物件可以同时有好几个主人,而当那个物件你不要用的时候,则使用release或是autorelease方法来把这个拥有的关係给断绝掉,準备把物件清掉并把佔用的记忆体还给系统。直接来看段範例:
int main (int argc, const char * argv[]){ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSNumber *n = [[NSNumber alloc] initWithInt:100]; NSLog(@"the retain count is %d", [n retainCount]); [n retain]; NSLog(@"the retain count is %d", [n retainCount]); [n release]; NSLog(@"the retain count is %d", [n retainCount]); [n release]; [pool drain]; return 0;}
输出结果:
the retain count is 1the retain count is 2the retain count is 1
除了retain/release之外,如果被collections,例如array、dictionary或set等等给拉进去的话,它的retain count也会加1;相对的,从Collection里拿出来的话,它的retain count会减1。
int main (int argc, const char * argv[]){ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSMutableArray *array = [[NSMutableArray alloc] init]; NSNumber *n = [[NSNumber alloc] initWithInt:100]; NSLog(@"the retain count of n is %d", [n retainCount]); [array addObject:n]; NSLog(@"the retain count of n is %d", [n retainCount]); [n release]; NSLog(@"the retain count of n is %d", [n retainCount]); [array release]; [pool drain]; return 0;}
输出结果:
the retain count of n is 1the retain count of n is 2the retain count of n is 1
在这个例子里可以看到,当使用addObject方法把n放进array之后,它的retain count会加1;当使用removeObjectAtIndex把物件从array移出来的时候,它的retain count会减1。最后,当array本身收到release的时候,它会对目前全部的内容物发送release讯息。所以在上面的例子来说,如果在[array release]之后再想存取n变数,就会出现错误讯息。
记得,你retain了一个物件,确定没要再用之后就release掉。retain跟release的次数通常是成对的,你手动retain了几次,到时候就得手动release几次。
有一些常见可能会发生问题的写法:
- (void)reset{ NSNumber *zero = [[NSNumber alloc] initWithInteger:0]; [self setCount:zero];}
这里用alloc建立物件,却没有对等的release,可能造成memory leak。
- (void)reset{ NSNumber *zero = [NSNumber numberWithInteger:0]; [self setCount:zero]; [zero release];}
在这里numberWithIngeter产生的是一个autorelease物件,所以这并不属于你拥有的,当你对它执行release它可能就会因为retain count变成0而被清掉,在autorelease pool里因为会再对所有标记为autorelease的物件再送一次release讯息,这时候就会出错了。请记忆体释放的原则:「You only release or autorelease objects you own.」,如果物件不属于你,就不要随便对它执行release方法。
关于Autorelease pool,你可能会在一些刚建立好的专案里看到它帮写好几行程式码了,autorelease pool并不是真正的GC机制,它比较像是GC的替代品。简单的说,当你对物件执行autorelease方法时,就是把该物件标记成”待会再释放”的物件,在每个run loop或是pool drain的时候就会对这些有标记的所有物件发送release讯息。虽说是替代品,但有些地方还是非用不可。
结论:
在你跟系统要了一块记忆体来用的当下,请先养成「我什么时候会还回去」的习惯。
retain/release通常都是成对的,手动做了N次的retain,就记得要做N次的release。
如果你要某个物件,就retain它;如果不用了,就release它,把记忆体还回去。