delegate 之assign & retain讨论

作者QQ:415074476

QQ群:191280586

delegate 的property属性到底该用assign 还是retain?

 

网上有说法是该用assign.

但我在实际工作中时常遇到crash的情况(包括umeng,sian,tencent sdk)。另外,在cocoa的api中,异步方法调用时,delegate的retainCount会+1,说明,在这种情况下,其实是使用的retain.

 

本课题就是结合官方文档,官方api测试。自由Delegate设计测试,来给出一个最终答案。

 

首先谈谈网上的看法。

现在网上都是认为应该是assign. 避免循环计数。

这个很容易理解的。。

案例一:

对象A 持有对象B.

对象B有个delegate属性,指向的对象是对象C.

假设delegate为retain的,对象C是对象B本身。

 

描述下引用计数:

对象A 持有对象B.(对象B计数为1)

对象B有个delegate属性(retain),指向的对象是对象B本身.(对象B的计数为2).

 

Ok.现在对象A释放。调用dealloc方法。释放对象B。(对象B的引用计数减低为1).

然后呢?没有然后了。

 

因为delegate的释放是在对象B调用dealloc方法时。

-(void)dealloc

{

….

[delegate release];

[super dealloc];

}

 

明显对象B这个对象占的内存泄露了。

如果有读者用过umeng的Sdk,就会发现他们是怎么处理的呢。

在对象A的dealloc方法做了处理, 不光处理了对象B本身,还把他的delegate置为了nil. 否则会crash哦。

 

好吧。我给的这个案例。不把delegate置为nil,也不会crash的,只会有内存泄露。

 

大家使用cocoa api时,会在dealloc方法里,去把delegate置为nil么?不会。

所以,这种折中的方法显然行不通。挖苦点是太山寨,太不安全。

 

到底该如何设计?

既然上面的例子用retain不行,是不是说明网上的说法是正确的。Delegate的属性必须为assign, 不然会造成内存泄露。

 

好吧。我给出另一个案例:
对象A 持有对象B.

对象B持有 对象C.

对象C有个delegate属性(assign), 指向对象B 。delegate声明了方法callMe.

对象B实现方法callMe.

一般情况下, 以上代码不会有问题。

但在有异步调用就容易出问题了。

假设对象C 有个方法是start. 发起一个异步请求,获取到资源后,在调用callMe方法。

调用栈如下:

[B.C start];

[C.delegate callMe];

由于异步的关系。上述两行代码之间会发生什么事呢?

可能对象B释放掉了,并且它的内存被其它对象所占用。

那么

[C.delegate callMe]一定crash.

怎么办?加上if(delegate && [delegate respondsToSelector:@selector(callMe)])

{

//doSometing

}

 

很遗憾地告诉你。 这样是真不行, 照样crash.

怎么办?既然assign会因为对象的失去而crash, 那我retain呗。 问题回去了,又是内存泄露。

不信?看看:

对象A 持有对象B.(对象B引用计数1)

对象B持有 对象C.

对象C有个delegate属性(assign), 指向对象B.( 对象B引用计数2).

 

对象A调用dealloc.

对象B引用计数减少为1.

然后呢?没然后了。。泄露。

 

因为必须对象B引用计数减少为0时,才会调用dealloc方法。才会release对象C。然后对象在dealloc方法里release delegate.

 

都行不通啊。。

 

我认为assign是没错的。是下面的判断方法不对。

if(delegate && [delegate respondsToSelector:@selector(callMe)])

{

//doSometing

}

 

到底该如何判断呢?

NSObject协议有下面两个方法。

– (BOOL)conformsToProtocol:(Protocol *)aProtocol;

 

– (BOOL)respondsToSelector:(SEL)aSelector;

NSObject对象有下面这个方法:

–   (void)doesNotRecognizeSelector:(SEL)aSelector;

到底哪个方法是有效的呢?

一一测试下吧。

 

– (BOOL)respondsToSelector:(SEL)aSelector;

都不可行。

因为占据内存的可能是一个基本数据类型。

 

看来得分情况了。

一:同步方法用assign.

二:异步方法。在发请求时retain,响应时release. 这种情况就不应该用property属性。看下官方的异步方法里要传delegate参数的Api,是不是在头文件里,找不到delegate的property属性呢。

怎么设置呢? 传参。启动方法里传参。 这才是正确的方法。

有不同看法的,欢迎讨论。

 

 

 

 

 

 

Tags:
One Comment