iOS开发之回调delegate的方法时判断delegate是否已经被释放

最近的项目遇到了网络请求,需要在请求完成后回调delegate的方法。然而回调时经常遇到这种情况:delegate已经被释放,这时调用其方法则会引起crash

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。这两个函数各有优劣。使用第一个函数判断类型是否改变的优点是在 iphone开发环境下默认公开,可以随便调用,缺点是要使用几字节的内存空间用于存放字符串,而且做字符串比较要稍微多花费一些CPU时间。第二个函数 优点是可以将获取的Class指针做为int型保存起来,只需要4字节,且比较起来节约CPU时间,坏处是我们要手动声明一下此函数才可以在自己的代码里 使用,否则会出现一个warning,提示“Implicit declaration of function ‘object_getClass’ is invalid in C99”,不过手动声明一下只要加一行代码就可以,也不麻烦。

下面是一个实例:

// WebService.h

#import <Foundation/Foundation.h>

@protocol ServiceDelegate;

@interface WebService : NSObject {
    id <ServiceDelegate> _myDelegate;
    Class _originalClass;
}

@property (nonatomic, assign) id myDelegate;

- (void)postDataWithURL:(NSString *)myURL postData:(NSDictionary *)dataDic setDelegate:(id)theDelegate;
- (void)serviceFun:(NSDictionary *)paramDic;

@end

@protocol ServiceDelegate <NSObject>
- (void)serviceCallBack:(id)resultObject serviceFlag:(NSInteger)flag;
@end
// WebService.m

#import "WebService.h"

Class object_getClass(id object);

@implementation WebService

@synthesize myDelegate = _myDelegate;

- (void)postDataWithURL:(NSString *)myURL postData:(NSDictionary *)dataDic setDelegate:(id)theDelegate
{
    self.myDelegate = theDelegate;
    _originalClass = object_getClass(theDelegate);
    [NSThread detachNewThreadSelector:@selector(serviceFun:) toTarget:self withObject:dataDic];
}

- (void)serviceFun:(NSDictionary *)paramDic
{
    Class currentClass = object_getClass(self.myDelegate);
    if (currentClass == _originalClass) {
        // 如果delegate没有被释放
    }
}
 
 
第二种方法

http://pingguohe.net/2011/09/01/howtojudgewhetheradelegateisreleased/#comment-983

困惑了相当长时间的一个问题了,实际上在Xcode4中会出现 

if ((int)delegate->isa == classIsa) { 

这行报错,member reference base type 'id<HTTPRequestDelegate>' is not a structure or union

因为ide不认为它是NSObject对象,只要对它转为NSObject对象即可。

//************************************************************************************

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

这个需要是因为最近在做网络请求的底层,需要在请求完成时回调某delegate的某方法。
然而回调时经常遇到这种情况:delegate已经被release了。如果delegate已经被dealloc掉,则无法调用其方法,否则引起程序crash。

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

首先,我们此时无法用if (nil = delegate)判断delegate是否已经被dealloc掉,因为被dealloc之后,delegate对象也不是空的,大部分情况下是一个objc_object*类型的C指针。

其次,我们又会想到在本对象中先对delegate retain一次,这样回调时不会崩溃了。但是这样会出现一个retain cycle,本对象和delegate都永远不会被释放了。

再次,我想到是否可以用isKindOfClass判断是否被dealloc。然而此时也不能用[delegate isKindOfClass]判断是否已经被dealloc,因为isKindOfClass是NSObject协议中的方法,此时delegate如果不是NSObject,对其发送isKindOfClass消息会导致crash。

此时很小部分情况下,delegate会是NSObject,可能是NSDictioary,也可能是原本的类。而大部分情况下,delegate已经不是NSObject。所以此时任何形式的[delegate method]都会导致crash,因为任何的[delegate method]的前提都是:delegate是一个NSObject。

无奈之下我又想到,使用delegate->isa判断delegate是不是NSObject。这里介绍一下,objective-c中所有对象都是结构体,每个结构体中都有一个名为isa的指针指向其类。而类也是一种结构体,类的isa指向其父类。处于最底层的结构体是无isa的,NSObject的isa指向的也是NSObject。isa具体的值是运行时确定的。
一开始的思路是用delegate->isa->isa->isa->…一直指下去,如果isa与NSObject的isa相同,则说明delegate是一个NSObject。但是这样是行不通的,因为如果delegate不是NSObject,只是objc_object*,一直指下去却指不到NSObject的话,总会指到最底层的结构体,而此结构体无isa,如果访问结构体内没有的东西,程序又会crash了。

说了这么多,结论就是这个问题很是蛋疼。再做不出来我就要把释放本对象的责任交给用户了。

等等,如果在本对象初始化后,delegate传进来时保存delegate的isa,此时delegate一定未被dealloc(为什么?因为是单线程的),在回调时判断delegate此时的isa和当时保存的isa是否一样,就可以解决了。
代码如下:
协议声明:

@protocol HTTPRequestDelegate <NSObject>
 
@optional
 
- (void) requestDidLoadResponse:(NSString *)responseDictionary;
- (void) requestDidFailedLoadResourceURLWithError:(NSError *)error;
 
@end

类声明:

@interface MyClass : NSObject <FooDelegate,BarDelegate>
{
    ...
    int classIsa;
    id <HTTPRequestDelegate> delegate;
}
 
@property (nonatomic,assign) id <HTTPRequestDelegate> delegate;
 
@end

类实现:

@implementation MyClass
 
@synthesize delegate;
 
- (id)initWithDelegate:(id)requestDelegate
{
    self = [super init];
    if (self) {
        //TODO:Send request,etc.
        self.delegate = requestDelegate;
    }
    return self;
}
 
- (void)setDelegate:(id<HTTPRequestDelegate>)iDelegate
{
    delegate = iDelegate;
    NSString *delegateDescription = [[iDelegate class] description];
    classIsa = (int)objc_getClass([delegateDescription UTF8String]);
}
 
- (void)callback
{
    if ((int)delegate->isa == classIsa) {
        if ([delegate respondsToSelector:@selector(requestDidLoadResponse:)]) {
                NSString *responseString = @"foobar";
                [delegate requestDidLoadResponse:responseString];
        }
    }
}

然而由于多线程的原因(发出请求和回调发生是在两个线程上的),会有极少数的情况(测试中发生概率在万分之一以内,和CPU有关)在if ((int)delegate->isa == classIsa)判断时,delegate当前的isa会和本对象初始化时isa相等,也就是说delegate未被dealloc,而调用回调时,delegate已被dealloc,导致程序crash。避免这种小概率事件的方法是,在delegate中发送请求前[self retain]一下,然后在回调到达时[self release]一下,这样除了避免崩溃以外,还会确保请求已经发送完毕,不会被发送一半。

以上。

–OpenThread


  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
回答: 在iOS开发中,可以使用Block和delegate来实现一对多的回调。使用delegate,首先需要定义一个delegate协议,其中包含回调方法。然后在需要进行回调的地方,设置delegate并调用回调方法。具体实现可以参考以下代码示例:\[2\] ``` // 定义delegate协议 protocol FirstCellDelegate: class { func firstCellBtnTap(_ cell: firstTableViewCell) } // 在需要进行回调的地方设置delegate并调用回调方法 weak var delegate: FirstCellDelegate? // 调用回调方法 if let delegate = self.delegate { delegate.firstCellBtnTap(self) } // 实现delegate回调方法 func firstCellBtnTap(_ cell: firstTableViewCell) { let cellIndexPath = tableView.indexPath(for: cell) print("delegate回调 section:\(cellIndexPath?.section ?? 0), row:\(cellIndexPath?.row ?? 0)") } ``` 另外,使用Block也可以实现一对多的回调。Block可以作为函数表达式传递给API,可以选择性地存储,并且可以被多个线程使用。Block不仅包含了在回调需要执行的代码,还包含了执行期间所需的数据。具体实现可以参考以下代码示例:\[3\] ``` // 定义Block回调 typealias CallbackBlock = () -> Void // 在需要进行回调的地方使用Block进行回调 var callback: CallbackBlock? // 调用回调 callback?() ``` 通过使用Block和delegate,可以实现一对多的回调,满足不同场景下的需求。 #### 引用[.reference_title] - *1* [Block实现iOS回调](https://blog.csdn.net/feelinghappy/article/details/119870367)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [iOS Block与Delegate的用法,各自优缺点及使用场景](https://blog.csdn.net/huangshanfeng/article/details/82106580)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值