使用bitfield优化代理方法的调用

前言

在编写 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个二进制位, flagBflag 分别是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];
}

你可能感兴趣的:(使用bitfield优化代理方法的调用)