OC 中 Protocol 默认实现

Swift 中的协议可以在 Extension 里面添加默认实现。这个强大的功能使用过之后就一直觉得 OC 中的协议用起来不开心了;由俭入奢易,由奢入俭难呐~ 不过没关系,其实我们可以在 OC 中实现类似的功能

从 Category 说起

Swift 中通过协议的 Extension 来添加默认实现。OC 中虽然不能直接对协议添加 Category,但是我们也可以朝着这个方向上思考。通过 Category 的原理可知:一个类的 Category 会被编译器编译,然后在 Runtime 初始化时注入到相关的类中。我们可以模拟这一个过程来实现对协议增加分类

模拟 Category 增加默认协议实现的流程:

  1. 创造一个类继承并实现协议的方法和属性 –> 模拟 Category 结构体用于编译时保存信息
  2. 在 Runtime 初始化时将该类中的方法和属性注入到遵守协议的类中 –> 模拟 Category 方法和属性等注入

创建协议默认实现类

先创建一个类实现默认的协议;我们有如下几个需求:

  • 由于会有多个协议,需要多个类与之对应,否则协议中有相同定义的方法就会出现问题。所以类名不能重复
  • 该类要有特殊的标识,以便我们在 Runtime 初始化时能将它与对应的协议联系起来
  • 要能自由添加方法实现对应的协议,同时做到写起来如 Swift 般简单快捷

在 OC 中,满足上诉需求的最佳解决方案就是:宏是一种预处理指令,它提供了一种机制,可以用来替换源代码中的字符串;不重复的类名也可以通过宏的字符串拼接实现

1
2
3
4
#define Extension(PROTOCOL) \
interface PROTOCOL ## ProtocolDefaultClass : NSObject < PROTOCOL > {} \
@end \
@implementation PROTOCOL ## ProtocolDefaultClass

Extension 宏展开之后就是一个普通的类定义代码,同时还遵守了对应的协议,只需要在宏之后添加对应的方法实现即可

Runtime 注入

第二个困难点在于,如何能在 Runtime 初始化时将协议对应的类的方法注入到遵守协议的类中?

这个部分我们需要借助编译指令来实现,__attribute__((constructor)) 构造器声明可以声明一个函数在可执行文件(或 shared library)load 时被调用

1
2
3
4
__attribute__((constructor))
static void objc_protocol_default_methods_inject(void) {
// 注入方法到遵守协议的类中
}

实现

发现了一个库(extobjc)已经实现了这个功能,不重复造轮子了 。。。。。。

来解析一下它的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#define concreteprotocol(NAME) \
/*
* create a class used to contain all the methods used in this protocol
*/ \
interface NAME ## _ProtocolMethodContainer : NSObject < NAME > {} \
@end \
\
@implementation NAME ## _ProtocolMethodContainer \
/*
* when this class is loaded into the runtime, add the concrete protocol
* into the list we have of them
*/ \
+ (void)load { \
/*
* passes the actual protocol as the first parameter, then this class as
* the second
*/ \
if (!ext_addConcreteProtocol(objc_getProtocol(metamacro_stringify(NAME)), self)) \
fprintf(stderr, "ERROR: Could not load concrete protocol %s\n", metamacro_stringify(NAME)); \
} \
\
/*
* using the "constructor" function attribute, we can ensure that this
* function is executed only AFTER all the Objective-C runtime setup (i.e.,
* after all +load methods have been executed)
*/ \
__attribute__((constructor)) \
static void ext_ ## NAME ## _inject (void) { \
/*
* use this injection point to mark this concrete protocol as ready for
* loading
*/ \
ext_loadConcreteProtocol(objc_getProtocol(metamacro_stringify(NAME))); \
}

在宏定义的时候创建对应的注入方法;同时在默认实现类的 + (void)load 方法中将 Protocol 以及对应的默认实现类的注入 Block 封装在起来缓存到一个全局数组中

我之前的想法是在 Runtime 初始化完成之后遍历所有的类,找到其中按照特殊规则命名的即是协议默认实现类,进一步拿到对应的协议;然后再将类中的方法同步到遵守协议的类中

  • 对比两种方案,extobjc 将协议及对应注入 Block 存储在全局数组中,在处理协议继承的情况时更加方便;同时通过对全局变量加锁可以包装线程安全

__attribute__((constructor)) 声明的方法会在 Runtime 初始化完成之后调用,所以会在 +(void)load 之后执行

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2017-2021 HonQi

请我喝杯咖啡吧~