热修复-JSPatch源码

JSPatch

简单版本的JSPatch和注释:https://github.com/misaka14/JSPatch_simple

学习JavascriptCore

一、了解JavascriptCore

JavaScriptCore框架 是一个苹果在iOS7引入的框架,该框架让 Objective-CJavaScript 代码直接的交互变得更加的简单方便。

JavaScriptCore是苹果Safari浏览器的JavaScript引擎,或许你听过GoogleV8引擎,在WWDC上苹果演示了最新的Safari,据说JavaScript处理速度已经大大超越了GoogleChrome,这就意味着JavaScriptCore在性能上也不输V8了。

JavaScriptCore框架其实就是基于webkit中以C/C++实现的JavaScriptCore的一个包装,在旧版本iOS开发中,很多开发者也会自行将webkit的库引入项目编译使用。现在iOS7把它当成了标准库。

JavaScriptCore框架在OS X平台上很早就存在的,不过接口都是纯C语言的,而在之前的iOS平台(iOS7之前),苹果没有开放该框架,所以不少需要在iOS app中处理JavaScript的都得自己从开源的WebKit中编译出JavaScriptCore.a,接口也是纯C语言的。可能是苹果发现越来越多的程序使用了自编译的JavaScriptCore,干脆做个顺水人情将JavaScriptCore框架开放了,同时还提供了Objective-C的封装接口。

二、使用JSContext

1、OC调用JS的方法
  • 1、新建一个全局的webViewjsContext,并初始化webView
    image.png
  • 2、加载一个HTML文件
    image.png
  • 3、运行项目的效果,如果图有一个按钮是JS调用OC
    image.png
  • 4、初始化jsContext,一般初始化需要在网页加载完毕的时候调用,所以需要设置webView的代理,实现webViewDidFinishLoad:的方法,可以观察到js中的testOCToJS实现的是把第三个参数c的值赋值idh1DOM的字体颜色,并且把a+b的值返回

    image.png

    • 34行:初始化jsContext对象
    • 35行:获取testOCToJS方法对象
    • 36行:调用testOCToJScallWithArguments并带上参数,参数是数组的形式,callWithArguments返回值表示是js中的testOCToJS的返回值
  • 5、所以控制台打印为3,模拟器运行是绿色

    image.png

    image.png

2、JS调用OC方法
  • 1、如图中,有一个按钮,按钮点击事件为testJSToOC
    image.png
  • 2、block实现的方式,在回调可以接收到参数


    image.png

JSPatch

一、简单看一下官方的DEMO

  • 1、新建JPViewController并在里面添加一个按钮,点击事件为handleBtn:,但是在方法实现内,并没有做任何事情

    image.png

  • 2、demo.js

    image.png

  • 3、如果我们要处理,点击Demo中的按钮,要跳转至AViewController。学过runtime的应该知道,可以利用method_exchangeImplementations
    class_replaceMethod的处理方式

  • 4、通过以下代码,在NSobject中的分类添加了wt_handleBtn:的实现,并交换了JPViewController中的handleBtn:的实现。点击JPViewController中的按钮,发现控制台确实打印了信息,表明替换成功。

    image.png

    image.png

二、源码分析
  • 1、在demo中,我们可以发现在应用程序启动时候,调用了JPEnginestartEngine方法
    image.png
  • 2、研究了startEngine发现,它在里面判断JSContext是否为空,如果为空,则new,并且context监听pobt_OC_defineClassJS方法,如果不为空则return返回。接下来先看_OC_defineClass

    image.png

  • 2.1、在startEngine函数底部发现,加载了JSPatch.js文件

    image.png

  • 3、学习上面JSContext的同学应该发现,这个_OC_defineClassdemo.jsdefineClass有点类似,理论这两个方法名应该是一致的
    image.png
  • 4、翻阅查询__OC__,在JSPatch.js文件中发现,发现其实defineClass并没有直接调用JSContext,而是调用到JSPatch.js中的全局函数defineClass,函数中会调用JSContext中的_OC_defineClass
    image.png
image.png
  • 4.1、如上图,defineClass函数内部调用了_formatDefineMethods函数,可以发现,其实是取出对象方法,并且把方法实现,重新包装一个数组里面有两个值,一个是参数的个数,第二个是一个函数的实现
    image.png
  • 5、接下来回调到OC中的defineClass,传进这个三个参数,类名[对象方法][类方法],并返回一个字典。这个字典会返回给JS,具体干什么的。。。。。
    image.png
  • 6、首先获取类对象


    image.png
  • 7、for循环两次遍历,第一次遍历对象方法,第二次遍历类方法。

主要取出这个类对象、对象方法,然后调用overrideMethod方法

image.png

三、总结分析

替换、添加方法流程
  • 1、在didFinishLaunchingWithOptions中启动了[JPEngine startEngine],执行了JSPatch.js

    image.png

  • 2、在JSPatch.js中,利用Object.defineProperty方法,给Object对象添加了_customMethods属性里面定义的__csuperperformSelectorInOCperformSelector4个方法

    image.png

  • 3、在JSPatch.js中,也定义了一个全局方法defineClass, 这个方法是函数。

    image.png

  • 4、在didFinishLaunchingWithOptions中,会加载调用demo.js中的方法

    image.png

  • 5、demo.js中的有两个defineClass函数, 调用_OC_defineClass函数,这会触发JSContext,会回调到defineClass

    image.png

    image.png

  • 6、主要遍历当个对象的,需要被替换、添加的对象方法、类方法,具体实现在overrideMethod

    image.png

  • 7、利用methodSignatureForSelectorforwardInvocation做消息转发的简单案例

    image.png

    image.png

  • 8、overrideMethod函数步骤。

    • a、把forwardInvocation的实现替换成了JPForwardInvocation
      image.png
    • b、如果有实现handleBtn:的话,就添加方法ORIGhandleBtn:
      image.png
    • c、把方法hanleBtn:实现,直接转发进入消息转发流程,由于forwardInvocation被修改为JPForwardInvocation,所以就走JPForwardInvocation的流程
      image.png
触发按钮事件的流程
  • 0、流程图


    image.png
  • 1、点击按钮会进入JPForwardInvocation函数
  • 2、取出要调用的JS函数


    image.png
  • 2.1、声明参数数组,并且把方法的调用者包装成JPBoxing对象


    image.png
  • 2.2、把参数数组转换成JS对象数组


    image.png
  • 2.3、获取方法的返回值


    image.png
  • 2.4、方法返回值为空,


    image.png
  • 3、声明一个参数数组,第一个是自己,第二个参数是UIButton

    image.png

  • 4、判断方法返回值,如果为v,代表是空值,再利用callWithArguments调用js

    image.png

    image.png

  • 5、把args第一个参数截取掉(也就是当前的类名),其中执行到originMethod.apply函数时,其实就是调用原来handleBtn:的对应的实现
    原始函数,并且把args剩余的参数传递进去

    image.png

通过正则转换__c

image.png

转换后的函数
image.png

  • 6、由于之前通完Object.definePropertyObject添加过_c函数,所以会进入_c函数实现,最后是返回了一个函数,其实在函数的时候后面加了(),所以直接执行这个函数

    image.png

  • 7、上面的函数内部调用了_methodFunc函数

    image.png

  • 8、_OC_callC类方法会调用,并且把类名、方法名、参数列表,再次回调给OC的callSelector的方法,以下是无参数的大概实现逻辑。
    无参数的大概实现逻辑

    image.png

主要做了三件事:

  • 1、获取是实例对象还是类对象
  • 2、获取方法签名并实例化NSInvocation对象
  • 3、设置NSInvocation的target
  • 4、设置NSInvocation的selector
  • 5、判断是否有参数,如果有参数,就设置NSInvocation的参数
  • 6、调用NSInvocation的invoke
  • 7、判断方法调用是否有返回值,从NSInvocation中获取返回值,并包装成JS对象并返回给JS

源码

  • 如果是实例对象,通过formatJSToOC方法,把实例对象转换成OC的实例对象


    image.png
  • 把JS传递过来参数,转换成OC对象或数据


    image.png
  • 获取类对象和要做消息转发的方法名


    image.png
  • 声明了要做消息转发的两个重要的对象,并初始化,并设置了target、selector


    image.png

    image.png
  • 取出方法的参数、跟js显式的参数个数对做比,如果显式的个数大于取出方法的参数的个数-2的话,说明方法是带参数的。为什么要方法的参数的个数-2,在switch逻辑中,可以看到设置了参数

    image.png

    image.png

  • 调用方法


    image.png
  • 获取方法的返回值,并判断返回的参数是否为空,是否为对象,如果是对象利用method getReturnValue:获取返回值

    image.png

  • 进入formatOCToJS的流程,并返回一个字典,第一个参数为真实的类名,第二个JPBoxing对象,里面有一个weakObj保存着实例化的对象


    里面有
  • 9、这样就又会回到第5个步骤

你可能感兴趣的:(热修复-JSPatch源码)