您的位置:首页 > 其它

NSInvocation动态调用任意block

2016-06-03 07:31 761 查看
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时, 就可以派上用场了~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: