您的位置:首页 > 其它

再谈Cocoa中回调delegate的方法时判断delegate是否已经被释放

2012-07-27 22:24 411 查看
我在Cocoa中回调delegate的方法时判断delegate是否已经被释放中描述了如何使用delegate的isa判断其所属类是否改变,从而判断delegate是否被释放。但是Nike指出:一旦此delegate注册过KVO,其isa就会被改变了。除非保证delegate不会被注册KVO(这要求判断delegate是否被释放时保证delegate必须是我们自己创建的,而不是其他任何人,而且我们要手工保证此delegate不会注册KVO),否则使用此种方法判断理论和实际上都是不可靠的。虽然iphone上的YAJL库目前也采用了这种方式判断对象类型,但是自从知道了KVO的那些事儿后,我觉得isa的方式还是很不靠谱的。

周末闲来无事读了读Objective-C Runtime Reference,发现objc的runtime中有两种判断类型的方式比较靠谱,他们可以直接取得任意一个objc_object(和id是完全一样的数据类型)的类或者类名。其函数如下:

//Returns the class name of a given object.
const char *object_getClassName(id obj);

//Returns the class of an object.
Class object_getClass(id object);


第一个函数可以返回任意一个id的类名,第二个函数可以返回任意一个id的Class。

而第二个函数会牵扯到isa-swizzling问题,参考八楼Nike大神的回复。

object_getClassName会不会牵扯到isa-swizzling问题我还没试过,没时间尝试~
以下为使用object_getClass判断delegate是否存在的一个例子,例子中MasterViewController将创建一个新线程试图回调DetailViewController的callback方法(涉及isa-swizzling,所以KVO下无效,但是下面例子的代码我没时间修改了)。

首先声明好用于回调的Protocol:

@protocol CallbackProtocol <NSObject>

@required
- (void)callback;

@end


然后在MasterViewController的成员变量里加上一个Class变量,用于存放delegate刚刚被设置进来时其Class:

@interface MasterViewController : UITableViewController
{
__unsafe_unretained id <CallbackProtocol> _delegate;
Class _originalClass;
}


在设置delegate的同时,将其Class保存在_originalClass成员变量中:

_originalClass = object_getClass(_delegate);


在回调时首先判断_delegate是否为空,若不为空再判断其类型是否改变,若类型已改变,则此对象已被释放(在Cocoa中回调delegate的方法时判断delegate是否已经被释放中已经有相关叙述)。

if (_delegate != nil) {
Class currentClass = object_getClass(_delegate);
if  (currentClass == _originalClass)
{
[_delegate callback];
}
else
{
NSLog(@"orginal:%d present:%d - Not same class",(int)_originalClass,(int)currentClass);
}
}
else
{
NSLog(@"Delegate is nil");
}


若成员变量不保存Class类型而仅仅将其指针保存为int则性能更优。

刚刚提到object_getClass没有被手动声明的话编译时此函数会被warning。为此我们需要自己手动声明此函数。不管你写在哪,我是写在了.m里。

在用了object_getClass的.m的#import语句之下,@implemention语句之上,加上下面两句话:

//Returns the class of an object.
Class object_getClass(id object);


至此大功告成。

回调demo下载地址:git://github.com/OpenFibers/CallbackDemo.git,demo中首先由master view进入detail view,再由detail view返回master view,返回后开辟新的线程,新的线程在3秒之后判断detail view是否存在。
以上

–OpenThread
December 4th, 2011 in 关于iOS

16 Comments

苹果核 » Blog Archive » Cocoa中回调delegate的方法时判断delegate是否已经被释放December 4th, 2011 at 23:18
[...] 然而如Nike的评论中所说,delegate一旦注册KVO,其isa是会改变的,所以如果我们不能手工保证其不会注册KVO,则使用此方法是不可靠的。使用其他运行时方法请参考这篇文章再谈Cocoa中回调delegate的方法时判断delegate是否已经被释放 [...]



junzhanJanuary 13th, 2012 at 11:49
牛逼



willonboyJanuary 19th, 2012 at 12:39
object_getClassName(id object)不可用, 我试过了, 不知道你是怎样作的. 我的环境是iOS 5.0, 只有object_getClass有效!



OpenThreadJanuary 29th, 2012 at 17:24
手动声明一下试试看亲



zh.enMarch 15th, 2012 at 17:38
亲测有效,不过没有很广泛的测试,谢谢楼主。

我的应用场景是这样的:我的一个view对model对象添加了观察者,当model的属性改变时view就会自动更新自己的内容。当view的dealloc方法被调用的时候我将从model中移除这个观察者,这个时候就会出现无法断定model是否已经释放的情况。



zh.enMarch 15th, 2012 at 17:53
楼主,刚又测试了下,这种情况下的object_getClass()取得的class在KVO还是会改变的,这就又回到了您上一篇说到的isa问题



AnonymousMarch 26th, 2012 at 11:44
这样的问题,个人认为只会出现在多线程中,比较合适的做法是在dealloc中将delegate = nil;



NikeMarch 27th, 2012 at 12:26
看的出,作者是个有钻研精神的开发者。

但非常遗憾,object_getClass 并不会隐藏 Objective-C 的 IsA-swizzling 特性,所以实际上用object_getClass并不会有任何改进。另一方面说的,程序保存的delegate可能在释放之后分配给其它Objective-C对象,也可能释放之后完全变成一个完全无意义的值,如果这种情况你调用了object_getClass,将导致不可预期的结果。

我还是那句话:当我们遇到判断Delegate有效性出现困难时,往往应该想想我们是不是已经滥用了Delegate。
当你回头看你过去写的东西,觉得非常幼稚时,说明你已经进步了。作者加油!



OpenThreadApril 5th, 2012 at 23:37
话说object_getClassName会涉及isa-swizzling不?~~~Nike大神求邮箱~



NikeApril 16th, 2012 at 23:17
object_getClassName当然涉及isa-swizzling。

似乎你没有仔细看我的前两次回帖。实际上你在通过你的方法判断某个指针对应的类之前,你首先要保证你保存的指针还是一个有效的Objective-C对象。但你目前并没有做这方面的考虑和判断。而且虽然利用Objective-C运行时特性可以有方法判断一个指针是否是一个Objective-C对象,但是效率不会太高,用于判断delegate的有效性并不划算。

我还是想告诉你:如果你的程序很容易出现由于delegate失效而导致程序Crash,除了考虑代码质量之外,还要考虑是不是滥用了delegate。



OpenThreadMay 25th, 2012 at 00:59
原来还有方法判断一个指针是否为objective-c对象,谢谢Nike指点~这几个月中对于这个问题的处理一直在采取ASIHTTP的方式,销毁自身前取消自身依赖的请求。而当时很想用运行时特性的原因是自动判断比手动取消回调优雅一些。至于效率问题,除非有大批创建销毁的动作,应该影响不会太大~而且也应该有方法用池和重用的方式避免大规模创建销毁

总之,感谢大神指点



NikeJune 8th, 2012 at 01:02
你在这个问题上走偏了。正确的做法就是你所说的“销毁自身前取消自身依赖的请求。” 你所谓的优雅方法在我看来类似于发现程序crash了,不去查找真正的原因,而是希望加个try - catch能够避免crash。



LostJune 21st, 2012 at 10:59
请教大神们,NSURLConnection中的delegate如果已经被释放,NSURLConnection是如何避免crash的?



OpenThreadJune 25th, 2012 at 17:33
了解了~感谢指点



OpenThreadJune 25th, 2012 at 17:36
“NSURLConnection是如何避免crash的?” +1



NikeJune 26th, 2012 at 12:17
我来回答Lost首先提出的问题:NSURLConnection 中的 delegate 如果已经被释放,NSURLConnection 是如何避免 crash 的?

其实 Apple 的 Cocoa 框架程序员很清楚,NSURLConnection 没有什么好办法保证NSURLConnection中的delegate对象已经被销毁(注意 我说的是销毁 dealloc,不是你说的释放)的时候,程序不crash。所以他们设计NSURLConnection是要 retain delegate 对象的。

对于这点:请看文档:

Special Considerations

The connection retains delegate. It releases delegate when the connection finishes loading, fails, or is canceled.

其实对于 NSURLConnection 的 delegate 实现是比较特殊的,不是简单的在 connection 中直接调用 delegate 对象的方法,因为 connection 和 delegate 运行在不同的线程里,所以这是一个需要借助于 runloop 的线程间 delegate调用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ss