http://www.wtoutiao.com/p/i7bspr.html
我们知道常规的block调用都是硬编码写死,参数类型必须在编译时匹配,编译器会转换成struct与C函数指针,比如下代码:
会被编译为:
假设我写这样的代码,则会出现编译错误:
id anyBlock = sumBlock;
anyBlock(1, 2, 3);
必须做强制转换才能编译通过:
id anyBlock = sumBlock;
((int (*)(int, int, int))anyBlock)(1, 2, 3);
而JavascriptCore可以很简单的设置任意数据类型的block, 让js调用. 比如:
那么block是怎么被动态调用的呢?
我们打个断点,可以看到如图堆栈:
你会看到竟然出现了-[NSInvocation invoke];也就是说, block是被NSInvocation调用的!
用过NSInvocation的应该知道, 它可以用来实现对指定对象的任意方法的调用, 类似如下常用方式:
可是对于block来说,它的selector是什么?如何设置它的selector?
我们把JavascriptCore源码拿下来, 看看究竟做了什么?
找到这两个函数,你是否猜到了调用方式?
并没有selector, 调用逻辑是:
获取block的signature->设置target为block->设置参数->invoke
我们按照这个思路写一段代码试试:
确实完成了block的调用!尝试成功了!
那么_Block_signature又是什么?
在JavascriptCore的ObjcRuntimeExtras.h文件中找到了如下代码:
原来_Block_has_signature是iOS的私有API,用来获取block的参数特征,可以看到JavascriptCore还用到了其他几个私有API.
如果我想用_Block_has_signature又担心被AppStore拒,怎么办?
淡定,既然block是一个struct,而objective-c runtime又是开源的,那么必然可以构造相同的struct然后解析里面的数据, 当然万能的github已经有CTObjectiveCRuntimeAdditions库提供此功能了.
使用很简单,把invocation的初始化改成如下代码即可.
顺便我们可以看下block的MethodSignature是什么样的:
可以看到参数的描述非常详细包含了参数个数、参数所占字节大小、参数内存地址偏移、参数类型标识等。
argument 0的符号是”@?”代表block, argument 1不是selector而是第一个参数, 所以setArgument是从1开始, 而不是2.
有了NSInvocation调用block,我们就可以实现在lua、js等脚本语言中调用获取到的任意OC block了!大致流程如下:
首先我们编写一个能调用任意block的函数:
然后我们将一个名为invokeBlock的函数注册给脚本语言,由他去接收和组装参数。
接着由invokeBlock将组装好的参数传给testInvokeAnyBlock并调用。
那么脚本中就可以实现block的调用了,类似如下代码:
至此我们通过断点的追踪、源码的分析、代码的尝试,弄清楚并实现通过NSInvocation动态调用任意block,当你需要在某些场景中动态调用OC的block时, 就可以派上用场了~