Delegate算是Objective-C的一大特性, 关于Delegate的基础就不多介绍了, 有兴趣的请参看文档.

这里仅对Delegate使用中的一些问题做点讨论

我们用Delegate很多情况下是基于多线程的,比如我们有一个ViewController在这个Controller里面进行了一个下载图片的操作,下载成功后需要通过protocol来现实下载成功, 但是当ViewController已经被release,而下载工作才结束, 那么下载工作的[delegate didFinishDownload] (暂且就这么命名吧) 就会产生一个异常,因为你给一个deallocated的对象发送了一个消息.

那么,如何解决这个问题呢,首先我们可能想到用if (delegate == nil) 来判断delegate是否存在,但其实这是不行的,因为已经dealloc的对象并不是nil.要知道Objective-C中给nil发送消息是可以的,所以如果这种方法可行,其实我们就根本不需要if这句,[delegate didFinishDownload] 给nil发送了一个消息也不会出现异常,因此这种方法只是重复了上面的错误.

还有一个叫[delegate respondsToSelector:SEL]来判断delegate是否响应一个Selector, 根据上一段的描述,我们也可以判断出这个也是不行的.这里额外提一点关于respondsToSelector的东西,要使用这个方法,必须有@protocol MyProtocol <NSObject>,因为respondsToSelector是NSObject的一个protocol方法.

既然要防止delegate被release,那么retain这个delegate是否可行呢?这么做虽然避免错误的发生,但是也产生了另一个问题,这就关系到Objective-C内存管理中的Retain Circle, 即:有A,B两个Object, A中有一个B的实例变量,B中又有一个A的实例变量,要release A就必须releaseA中的B,而要release B有必须release B中的A,这样就产生了一个Retain Circle,A B都不能被dealloc.解决Retain Circle的方法就是使用弱引用(weak reference),弱引用没有被引用的那个Object的所有权,也就不需要release它,从而解决了Retain Circle问题.为了防止Retain Circle的发生, delegate通常都是弱引用的, 因此我们一般不应该retain一个delegate.但是似乎有一个例外:NSURLConnection, 网上对其的讨论结果是:NSURLConnection会retain它的delegate,详细可以参考StackOverflow上的这个问题

似乎没有简单可行的方法来解决这个问题(至少在本文发表时我还没有找到),那么我们只能在通过程序结构的设计来解决这一问题了,对应不同的程序自然也就有不同的解决方法,我想到的一种就是在这个ViewConrtoller被release的时候,把下载方法中的delegate设置成nil即可(目前测试可行, 如有错误还请指正).

更新几种解决方法:

–以上–

Tagged with:  

17 Responses to Objective-C: delegate的那点事儿

  1. patgdut说道:

    用assign就好了嘛,至于NSURLConnection嘛,用ASIHTTPRequest代替。

  2. OpenThread说道:

    用restkit上传图片的时候也发现这个问题。
    假设delegate是一个UIViewController,在这个viewController被dealloc之后,delegate的类型是objc_object*,而dealloc之前,类型是UIViewController。
    绞尽脑汁想出来可以这样判断:
    if ([delegate isKindOfClass:[NSObject class]]) {
    //do whatever you want
    }
    或者精确定位到委托是否实现了需要的方法,可以这样判断:
    if ([delegate isKindOfClass:[NSObject class]] && [delegate respondsToSelector:@selector(request:didSendProgess:)]) {
    //do whatever you want
    }

  3. OpenThread说道:

    这次是真的解决了
    首先声明一个int型的成员:
    int classIsa;//用来保存delegate刚传入,未被dealloc时的类型
    在初始化后,delegate传入后
    NSString *delegateDescription = [[uploadDelegate class] description];
    classIsa = (int)objc_getClass([delegateDescription UTF8String]);
    在回调时,如果delegate还未被dealloc,与classIsa一致。若已dealloc,则类型为_NSCFDictionary或objc_object或其他类型。
    if ((int)delegate->isa == classIsa) {
    //callback
    }

    一开始打算用struct objc_object *foo;
    If(delegate->isa == foo->isa)
    {
    //callback
    }
    但是失败了。原因是申请出来的foo类型为NSURLConnection,很莫名其妙。

  4. 苹果核 » Blog Archive » Cocoa中回调delegate的方法时判断delegate是否已经被释放说道:

    […] 此篇文章中博客作者也有相同的问题:http://longtimenoc.com/archives/objective-c-delegate的那些事儿 […]

  5. […] 这个项目就是iOS 5,ARC中 __weak 的非ARC版。非常有助于解决delegate这种弱引用造成的EXC_BAD_ACCESS,关于这个问题可以参考一下我小时候发的一篇文章Objective-C: delegate的那点事儿 If you enjoyed this article, please consider sharing it! Tagged with: iOS • 心得  […]

  6. heyuan110说道:

    这里有办法可以参考:
    iOS开发之回调delegate的方法时判断delegate是否已经被释放
    http://www.wuleilei.com/blog/320

  7. kaholai说道:

    你对于Delegate的考虑非常有道理,我补充一点,按照ASIHttpreqeust库用Delegate的方法,他们现在也是不会去retain delegate的(之前是会retain的),根据苹果官网的说法,因为invoker 线程都已经挂掉,那么回调函数也没有执行下去的意义了,因此我们需要做的是从代码结构上面保证。
    思路:
    1. 调用者能存活到回调函数被调用时(可以考虑 单例 )
    2. 回调方法保存必要参数,invoker 做成与纯粹页面处理类。

    在使用ASIHttpRequest库时,我们也是要手动处理delegate的。

  8. ruiq说道:

    这个东西的解决办法只能是通过调整结构来避免吧
    对于每一次的网络请求封装成一个自定义的connection对象,该对象通过assign的方式持有delegate,然后再统一管理各个connection对象,这样可以完全避免循环引用。
    大概是这样吧。。。

  9. 吴挺说道:

    把协议写在appDelegate中也没用

  10. […] 此篇文章中博客作者也有相同的问题:http://longtimenoc.com/archives/objective-c-delegate的那些事儿 […]

  11. […] 此篇文章中博客作者也有相同的问题:http://longtimenoc.com/archives/objective-c-delegate的那些事儿 […]

发表评论

电子邮件地址不会被公开。 必填项已用*标注