




























文章目录
BlockHook 在业界已经率先解决了在同步调用场景下对 Objective-C Block 的 AOP 问题。但也有很多场景是需要先调用一段自己的逻辑,然后再异步延时执行 Block。
比如从外部跳转到 App 某个页面前需要检查下登录态,如果未登录则需要走完登录流程后才能继续跳转页面,而几乎所有基于 Block callback 的路由组件都没提供路由拦截器的功能。不同的路由组件内部实现不同,想要实现拦截器就需要针对不同的内部实现来修改路由组件源码。
因此我实现了 BlockHook 的异步拦截功能,所有基于 Block 的路由组件就都有了通用的路由拦截器!
当然,Block 拦截器的应用场景不仅于此。只要是需要『同步改异步执行』 Block 的场景都可以用到。
让子弹再飞一会儿!
BlockHook 拦截器用法很简单,在已有 BHInvocation 参数的基础上,增加了一个 completion 回调。当拦截器的逻辑异步执行完后,调用 completion 即可继续执行原来的 Block。如果拦截器的逻辑是同步的,也依然可以用这个接口,只是没必要罢了,推荐直接用原来的 block_hookWithMode:usingBlock: 接口。
1 | typedef void(^IntercepterCompletion)(void); |
举个例子,拦截时修改传入的参数,并延迟 0.5 秒再执行 Block:
1 | NSObject *testArg = [NSObject new]; |
首先想想如果要延迟一个 Objective-C 方法的执行,需要怎么做?
答案是利用消息转发机制,NSInvocation 调用 retainArguments 将方法执行所需的上下文持有,这样才能保证方法执行时所需的参数、target 等不会被释放。
对于 Block 来说,虽然也能通过 NSInvocation 来进行调用,但是经过 Hook 过后已经不再适用。因为 NSInvocation 的实现机制以及生命周期管理是个黑盒,且无法承载 Hook 相关的信息,需要自己来实现个 BHInvocation。

我之前的 BlockHook with Struct 这篇文章提到了个技术点:在 x86 架构下,当 Block 返回值是大于 16 Byte 的 struct 时,参数列表有些变化:

为了兼容这种情况,需要两套 args 和 retValue。一套『真的』用于传给 libffi 调用原始函数指针,另一套『假的』提供给使用方读写参数和返回值。这样使用方无需关心底层特殊逻辑,直接用就行了。
BHInvocation 主要结构如下:

PS:BHInvocation 与 NSInvocation 的场景和用法有些不同,所以实现上也会有差异。NSInvocation 没有公开源码,想了解原理的可以看看 mikeash 的实现: MAInvocation。但我并没有参考过 mikeash 的源码,因为等我写完了才发现它。。。
retainArguments 实现retainArguments 实现策略:
void **args 指针数组和返回值指针retain 指针内容类型为 Objective-C 对象的参数copy 过来strcpy 过来
需要注意的是这里依然要考虑两套 args 和 retValue 的问题。代码就不贴了,有兴趣的可以自己去看。
block_interceptor 实现解决了 retainArguments 的实现,一切都好说了。只要基于原有的 block_hookWithMode:usingBlock: 接口稍加改装即可:
1 | - (BHToken *)block_interceptor:(void (^)(BHInvocation *invocation, IntercepterCompletion completion))interceptor { |
写了这么多关于 BlockHook 的文章,我越来越发现自己在苹果爸爸面前所表现出的无知。几乎每一步都要去踩很多坑,看很多源码。而这次是看着苹果爸爸的文档脑补如何实现,业界也没有能参考的先例。
这种感觉犹如自己在黑暗中不断探索,并享受着这种孤独。
标题暗示着,这篇文章可能会有后续的。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。