前言
在编写 Objective-C
代码时, 代理
是我们经常使用的一种设计模式,它简单易用,善用它可令代码变的更易维护.
然而,在对 代理对象
发送 protocol
中的消息时, 出于代码的健壮性的角度考虑, 我们都应该先去试探 代理对象
能否响应此消息:
if ([self.delegate conformsToProtocol:@protocol(SomeProtocol)] && [self.delegate respondsToSelector:@selector(someProtocolMethod)]) {
[self.delegate someProtocolMethod];
}
注:苹果的大部分代理回调检验都只使用了
[self.delegate respondsToSelector:@selector(someProtocolMethod)]
,
例如 UITableView
的dataSource回调, 猜测这是苹果认为开发者对于一些基本的控件的接口都比较熟悉了, 一般不会发生在不知情的情况下就覆写掉 protocol
中的方法的场景, 然而, 当我们在定义自己的 protocol
并且调用其中方法时, 为了避免上述情况, 还是应该加上校验是否遵守了该 protocol
的判断.
然而, 当我们需要频繁的调用某个 protocol
中的方法时, 每次都进行如此判断合适吗? 仔细想想, 其实除了第一次的检测结果有用之外, 之后的校验判断可能多是多余的. 如果代理的对象没有发生改变, 那么极少情况下才会发生突然 遵守/不遵守 某个 protocol
,或者突然 响应/不响应 某个方法(比如在某些情景下利用 runtime
动态的遵守 protocol
, 动态的添加/删除方法). 鉴于此, 我们可以使用一些手段来将这些信息缓存起来, 来避免多余/重复的判断.
bitfield的使用
使用 bitfield 这一数据类型可能是实现这种缓存的最佳方案了.
我们可以利用 bitfield
声明结构体某个字段的所占用的二进制位数, 例如:
struct Flags {
unsigned int flagA : 4;
unsigned int flagB : 2;
unsigned int flagC : 1;
};
此时, 结构体 Flags
中, flagA
就占用了4个二进制位, flagB
与 flag
分别是2个和1个二进制位, 它们对应的存储范围分别是 0~15
, 0~3
, 0~1
.
显然这里使用二级制位数为1的字段来作为调用代理方法的校验缓存再合适不过了.
如此 我们可以定义一个这样的结构体
struct SomeDelegateFlags {
unsigned int delgateMethodA : 1;
unsigned int delgateMethodB : 1;
unsigned int delgateMethodC : 1;
};
在被委托对象中可以这么写:
// .h
@interface SomeClass : SuperClass
@property (nonatomic, weak) id delegate;
@end
// .m
struct SomeDelegateFlags {
unsigned int delgateMethodA : 1;
unsigned int delgateMethodB : 1;
unsigned int delgateMethodC : 1;
};
typedef struct SomeDelegateFlags SomeDelegateFlags;
@interface SomeClass ()
@property (nonatomic, assign) SomeDelegateFlags delegateFlags;
@end
@implementation SomeClass
- (void)setDelegate:(id)delegate
{
_delegate = delegate;
if ([delegate conformsToProtocol:@protocol(SomeDelegate)]) {
_delegateFlags.delgateMethodA = [delegate respondsToSelector:@selector(delgateMethodA)];
_delegateFlags.delgateMethodB = [delegate respondsToSelector:@selector(delgateMethodB)];
_delegateFlags.delgateMethodC = [delegate respondsToSelector:@selector(delgateMethodC)];
} else {
_delegateFlags.delgateMethodA = NO;
_delegateFlags.delgateMethodB = NO;
_delegateFlags.delgateMethodC = NO;
}
}
@end
而在调用相关代理方法时, 就不需要再校验是否遵守协议,响应消息了,可以读取该结构体的字段的缓存值来进行判断了.
if (_delegateFlags.delegateMethodA) {
[_delegate delegateMethodA];
}