天天看点

iOS热更新解读(三)—— JSPatch 之于 Swift继承自 NSObject 的 Swift 类纯 Swift 类Swift 原生类热修复难点

新建 swift 工程 <code>swiftjspatch</code>。

<code>appdelegate.swift</code>:

<code>viewcontroller</code>中设置两个自定义属性:<code>public</code>属性<code>a</code>,<code>private</code>属性<code>pa</code>:

<code>main.js</code>中去获取这两个自定义属性并各自赋新值,此外为<code>viewcontroller</code>继承自父类<code>uiviewcontroller</code>的属性<code>title</code>设置新值:

运行结果输出:

<code>title</code>修改成功,<code>a</code>获取成功,<code>pa</code>访问失败:找不到<code>selector(pa)</code>,查看 oc 端调用堆栈:

iOS热更新解读(三)—— JSPatch 之于 Swift继承自 NSObject 的 Swift 类纯 Swift 类Swift 原生类热修复难点

js端调试:

iOS热更新解读(三)—— JSPatch 之于 Swift继承自 NSObject 的 Swift 类纯 Swift 类Swift 原生类热修复难点

经过<code>_evaluatescript:withsourceurl:</code>处理,<code>main.js</code>中的方法都被替换成<code>__c('methodname')</code>。

iOS热更新解读(三)—— JSPatch 之于 Swift继承自 NSObject 的 Swift 类纯 Swift 类Swift 原生类热修复难点

<code>defineclass</code>对js对象method的改写也没问题。

由以上信息可知,jspatch 方法替换成功,方法调用环节js调用oc私有方法<code>pa()</code>也就是在<code>callselector</code>环节出错,获取不到方法签名导致后续消息转发无法进行。<code>public</code>方法则可以成功替换实现并调用。

现在在<code>private</code>变量前声明<code>dynamic</code>:

输出:

变量都被成功修改,也就是说方法替换和调用都没问题。

结论1:<code>jspatch</code>作用于继承自<code>nsobject</code>的类,其继承自父类的属性/自定义<code>public</code>变量可以直接访问和修改,自定义<code>private</code>变量需要加上<code>dynamic</code>。

从上个修改属性的案例已经看出对于继承自<code>nsobject</code>的类的继承自父类的方法,<code>jspatch</code>实现热更新是没问题的。所以直接看自定义函数的情况。

在<code>viewcontroller</code>自定义两个函数,其中一个是<code>private</code>方法:

<code>main.js</code>中对这两个自定义函数实现进行修改。<code>fun()</code>给a赋新值,<code>pfun()</code>给<code>pa</code>赋新值:

运行:

热更新失败!

iOS热更新解读(三)—— JSPatch 之于 Swift继承自 NSObject 的 Swift 类纯 Swift 类Swift 原生类热修复难点

从js调试结果看脚本是被执行过的,且「方法替换」成功,说明是oc端「方法调用」时没有走运行时的消息转发流程。

为两个函数添加<code>dynamic</code>声明:

hook成功:

swift 中静态函数分两种:class 函数/static 函数:

从结果看出,class 函数得到替换并调用成功,static 函数调用时没有进行消息转发:

新建<code>pure</code>类:

<code>main.js</code>修改<code>fun()</code>和<code>pfun()</code>的实现:

调用<code>call()</code>结果:

直接崩溃:

iOS热更新解读(三)—— JSPatch 之于 Swift继承自 NSObject 的 Swift 类纯 Swift 类Swift 原生类热修复难点

由上图知,<code>jspatch</code>在进行到<code>overridemethod</code>进行方法实现imp替换时要求<code>class</code>实现<code>nscoping</code>协议,而不继承自<code>nsobject</code>的swift类是不遵循该协议的,因此崩溃。

iOS热更新解读(三)—— JSPatch 之于 Swift继承自 NSObject 的 Swift 类纯 Swift 类Swift 原生类热修复难点

回到崩溃代码:

此处<code>jspatch</code>在初始化缓冲区的时候将<code>class</code>作为<code>dictionary</code>的<code>key</code>进行保存,而<code>dictionary</code>在设置<code>key-value</code>时会拷贝 <code>key</code>值,所以会导致给一个不遵循<code>nscoying</code>协议的对象发送了<code>copywithzone:</code>消息,导致崩溃。

到这里「方法替换」的步骤已经进行不下去了。<code>jspatch</code>对<code>swift</code>原生类的热修复已经无能为力了。但<code>swift</code>热修复的真正难点其实并不在这里,假如我们越过<code>nscoping</code>通过某种 swift style 的方式实现了对类中方法名和对应js实现的缓存,也就是完成「方法替换」的话,热修复就能成功了吗?

「方法调用」才是 swift 热修复中目前真正无解的地方,最大原因是<code>swift</code>中<code>runtime</code>相对oc中的<code>runtime</code>动态性大大减弱。

纯swift类没有动态性,但在方法、属性前添加dynamic修饰可以获得动态性。

继承自nsobject的swift类,其继承自父类的方法具有动态性,其他自定义方法、属性需要加dynamic修饰才可以获得动态性。

若方法的参数、属性类型为swift特有、无法映射到objective-c的类型(如character、tuple),则此方法、属性无法添加dynamic修饰(会编译错误)

swift类在objective-c中会有模块前缀。

另外最要命的一点:<code>objc_msgsend</code>函数无法用于 swift object。这个导致<code>jspatch</code>实现方法调用(消息转发)的基础机制在 swift 中失效了。

总结一下 <code>swift</code> 项目中使用<code>jspatch</code>需要注意的几点:

只支持调用继承自 nsobject 的 swift 类。

继承自 nsobject 的 swift 类,其继承自父类的方法和属性可以在 js 调用,其他自定义方法和属性同样需要加 dynamic 关键字才行。

若方法的参数/属性类型为 swift 特有(如 character / tuple),则此方法和属性无法通过 js 调用。

参考资料:

<a href="http://mp.weixin.qq.com/s?__biz=mza3odg4mdk0ng==&amp;mid=403153173&amp;idx=1&amp;sn=c631f95b28a0eb4b842a9494e43a30e5">swift runtime分析:还像oc runtime一样吗?</a>

<a href="https://github.com/bang590/jspatch/wiki/jspatch-%e5%9f%ba%e7%a1%80%e7%94%a8%e6%b3%95#11-swift">jspatch github wiki</a>

相关文章

<a href="https://yq.aliyun.com/articles/58873">ios 热更新解读(一)apatch &amp; javascriptcore</a>

<a href="https://yq.aliyun.com/articles/58874">ios 热更新解读(二)—— jspatch 源码解析</a>

继续阅读