之前也是对 delegate 怨念颇深——每次都需要检查 deleagte 并且检查是否响应了某个方法。不过那个时候的怨念是来自于 需要写那么一大段代码,而不是怨念性能。

过去的解决方法

当时的解决办法是写了这么一个

#define MBCCheckDelegate(methodName) (self.delegate && [self.delegate respondsToSelector:@selector(methodName)])

实际上写成 内联函数 也是可以的。不过到了这一步又开始有点犯懒了,所以一直没有继续。

优化

直到今天看到了 参考1 的这篇文章。文章中提到了可以缓存 respondsToSelector: 方法的结果,让我感慨原来还能这么做!

@protocol SomethingDelegate <NSObject>
@optional
- (void)something:(id)something didFinishLoadingItem:(id)item;
- (void)something:(id)something didFailWithError:(NSError *)error;
@end

@interface Something : NSObject
@property (nonatomic, weak) id <SomethingDelegate> delegate;
@end

@implementation Something {
    struct {
        unsigned int didFinishLoadingItem:1;
        unsigned int didFailWithError:1;
    } delegateRespondsTo;
}
@synthesize delegate;

- (void)setDelegate:(id <JSSomethingDelegate>)aDelegate {
    if (delegate != aDelegate) {
        delegate = aDelegate;

        delegateRespondsTo.didFinishLoadingItem = [delegate respondsToSelector:@selector(something:didFinishLoadingItem:)];
        delegateRespondsTo.didFailWithError = [delegate respondsToSelector:@selector(something:didFailWithError:)];
  }
}
@end

如上所示,便是使用 结构体 来缓存结果。我们也可以使用其他的数据结构来达到我们的目的。

然后在实际通过 delegate 对象调用方法时,只需要判断对应的属性即可。

但是有一点需要留意,Objective-C 可以动态添加方法,所以设置时的情况未必能代表调用时的情况。具体要不要用这种方法还要考虑团队内的开发氛围。

简写的 delegate

在有 protocol 之前,一般是给 NSObject 加一个 category 来声明 delegate 可以实现的方法。比如,现如今 CALayer 还有:

@interface NSObject(CALayerDelegate)
- (void)displayLayer:(CALayer *)layer;
// ... other methods here
@end

意思是告诉编译器,任何对象都可以实现 displayLayer: 方法。

如果这样写的话,在调用方法之前也要同样要用上面提到的 -respondsToSelector: 来检查。只要让 delegate 对象实现这个方法,然后把它保存在 delegate 属性里就行了,不用写 protocol 什么的。

苹果官方的库里有不少这样的东西,但是新写的代码最好还是用上面声明 protocol 的正规写法。
因为这种简写方法会污染NSObject(会干扰代码自动补全),并且也让编译器不易检查出打错字之类的 error。


参考

  1. [爆栈热门 iOS 问题——戴仓薯] 如何写好一个 delegate