release和retainCount

retainCount

昨天本来想为了讲obj-c的内存管理时,做一个简单的例子。开命令行,写obj-c文件,make,代码很简单:


#import <Foundation/Foundation.h>

int main(int argc, char* argv) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSObject* obj = [[NSObject alloc] init];
    NSLog(@"object retain count is:%d", [obj retainCount]);
    [obj release];

    NSLog(@"object retain count is:%d", [obj retainCount]);
    [pool drain];

    return 0;
}


然后命令行编译 gcc -framework Foundation main.m -o test,第一个NSLog输出是1,没问题,第二个NSLog输出居然也是1,但是这行之前,很明显已经把obj release了,按apple的文档介绍,当一个object的reference count到0后,会被dealloc掉,所以dealloc后再调用obj的retainCount应该程序crash掉。但是这里居然输出了1。

在输出最后一个NSLog之前如果加一句NSLog(@”other information”),那么后面的这句NSLog retainCount就会crash了。

其实这个完全是lucky,结果是不可预知,首先obj的确是被release了,引用计数也确实降到0了,只是obj只是一个指针,指针指向的memcache被dealloc后,相当于告诉系统这块内存可用了,但是接下来NSLog就要输出retainCount,可能此时这块内存的内容还没有变,而obj这个指针本身也没有指向nil(像C一样,free一个指针后,为安全把指针指向NULL,其实在obj-c中nil就是NULL,是个#define)。所以在此时,obj的指向还是那块内存,而那快内存的内容刚好还没有被其他占用,所以此时再调用它的retainCount,“巧合”的输出了上次的1。

而在这之前再加一个NSLog后,可能此行NSLog就刚好占用了obj指向那块内存,那么再下一个NSLog时,retainCount就crash了,但是这也是个“巧合”罢了。

同样的代码,我改在xcode里写,新建一个项目-> Command Line Utility -> Foundation Tool,main.m里输入同样的内容,command+R 编译运行,这次果然一次就正常的crash了,debugger console里显示:

objc[707]: FREED(id): message retainCount sent to freed object=0x103060

Program received signal:  “EXC_BAD_INSTRUCTION”.

sharedlibrary apply-load-rules all

(gdb)

但是一个正确的写法还是应该当一个obj retainCount为0后,把obj 赋值为nil,obj = nil,在[obj retainCount] 后,即使是nil也不会抛异常,实际上nil调用retainCount返回的结果是0。

其他

另外看了autorelease pool的drain和release,在《obj-c基础教程》书上写到,drain和release的区别是drain只适用于OS X 10.4后的版本,而release是各个Mac OS X的版本都可以用,所以我们使用release。我们看文档上有更详细的drain和release的区别:

drain
In a reference-counted environment, releases and pops the receiver; in a garbage-collected environment, triggers garbage collection if the memory allocated since the last collection is greater than the current threshold.

– (void)drain

Discussion
In a reference-counted environment, this method behaves the same as release. Since an autorelease pool cannot be retained (see retain), this therefore causes the receiver to be deallocated. When an autorelease pool is deallocated, it sends a release message to all its autoreleased objects. If an object is added several times to the same pool, when the pool is deallocated it receives a release message for each time it was added.

In a garbage-collected environment, this method ultimately calls objc_collect_if_needed.

Special Considerations
In a garbage-collected environment, release is a no-op, so unless you do not want to give the collector a hint it is important to use drain in any code that may be compiled for a garbage-collected environment.

Availability
Available in iOS 2.0 and later.

从上面的描述大概能得出这几个信息:

  • 在引用计数(非垃圾回收)的环境中,drain和release的效果是相同的
  • 在垃圾回收的环境中,release操作无效,使用drain则可以触发垃圾回收的操作
  • NSAutoreleasePool虽然继承于NSObject,但是和其他的NSObject有所不同,一个autorelesae pool不可以被retain。(经测试也不可以被autorelease)即不可以给一个autorelease pool发送retain和autorelease的消息。

语言是越用才能越了解这些细节。

– EOF –

0 comments:

Leave a Reply

Your email address will not be published. Required fields are marked *