最近有业务有需求需要为 UIViewController
的 UIViewControllerTransitioningDelegate
协议提供默认的实现,使用 swift 开发默认是支持给协议添加默认实现的,但是,目前主项目中使用的是 Objective-C 做开发,而 Objective-C 下是不支持给协议添加默认实现的。调研一番后,找到了合适的解决方案—— PKProtocolExtension
。
原理解析
宏定义
PKProtocolExtension.h
中的内容如下:
|
|
首先是定义了保留关键字 @defs
来封装真正的实现宏。defs
、 _pk_extension($protocol)
、 _pk_extension_imp($protocol, $container_class)
三个宏组合后的作用是创建自定义的容器类,然后实现协议的方法,这些实现即为该协议的默认实现。而容器类的名字则是由 _pk_get_container_class($protocol)
、 _pk_get_container_class_imp($protocol, $counter)
和 _pk_get_container_class_imp_concat($a, $b, $c)
三个宏来决定。这里面的 __COUNTER__
是累加数字宏,默认是 0 ,每调用一次,就会在之前的结果上加一。 ##
是连接符,将符号两边的参数连接起来,如果有一个参数为空,它会自动把空余的部分吃掉。以上面的代码为例,如果 $b 为空,则实际的字符串为 $a_$c 。
为了更直观地看到宏的作用,我们来写一个例子:
|
|
展开宏定义后如下:
|
|
提取协议方法及实现
从宏定义预编译之后的代码可以看出,核心方法是 _pk_extension_load
。该方法的作用是将容器类遵循的协议方法和实现保存到一个全局数组 allExtendedProtocols
中,数组元素的类型定义为一个结构体 PKExtendedProtocol
:
|
|
提取的逻辑处理则是依赖于 _pk_extension_create_merged
和 _pk_extension_merge
方法:
|
|
注入协议的方法和实现
成功提取相关协议的方法和实现后,怎么注入并且何时注入到相应的类中呢?我们知道在 main 方法调用之前,所有的类都已经注册到 runtime 中了,那么,在执行 main 方法之前去注入无疑是比较恰当的时机,但是,这个时机又必须得比加载 load 方法晚,否则,还没提取完协议方法,就到了注入环节无疑是不正确的。要完成这个目标,就需要借助于 __attribute__((constructor))
。被该属性修饰过的方法会在 load
方法之后 main 方法之前被调用。我们在注入方法中获取所有注册类,然后循环遍历找到符合条件的类,再将对应的协议方法和实现注入到该类中即可。
|
|
总结
protocolExtension
依赖于 runtime
,在 load
方法执行之后向已经在 runtime
中注册类中挑选合适的类进行注入操作。但是,这种做法本身也有一些值得商榷的地方,比如,在注入方法中遍历所有已注册的类的操作,本身就是耗时操作,会影响 App 的启动速度。有想过使用 runtime
的消息转发机制来进行懒加载,但是,这么做的问题在于如果某个实现子类重写了消息转发机制的相关方法,并且没有调用父类的相关方法,则该类的注入操作便会失败。