JSPatch
简单版本的JSPatch和注释:https://github.com/misaka14/JSPatch_simple
学习JavascriptCore
一、了解JavascriptCore
JavaScriptCore
框架 是一个苹果在iOS7
引入的框架,该框架让Objective-C
和JavaScript
代码直接的交互变得更加的简单方便。
而
JavaScriptCore
是苹果Safari
浏览器的JavaScript
引擎,或许你听过V8
引擎,在WWDC
上苹果演示了最新的Safari
,据说JavaScript
处理速度已经大大超越了Chrome
,这就意味着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、新建一个全局的
webView
和jsContext
,并初始化webView
image.png
- 2、加载一个
HTML
文件
image.png
- 3、运行项目的效果,如果图有一个按钮是
JS
调用OC
的
image.png
-
4、初始化
jsContext
,一般初始化需要在网页加载完毕的时候调用,所以需要设置webView
的代理,实现webViewDidFinishLoad:
的方法,可以观察到js
中的testOCToJS
实现的是把第三个参数c
的值赋值id
为h1
的DOM
的字体颜色,并且把a+b
的值返回
image.png
- 34行:初始化
jsContext
对象 - 35行:获取
testOCToJS
方法对象 - 36行:调用
testOCToJS
的callWithArguments
并带上参数,参数是数组的形式,callWithArguments
返回值表示是js
中的testOCToJS
的返回值
- 34行:初始化
-
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
中,我们可以发现在应用程序启动时候,调用了JPEngine
的startEngine
方法
image.png
-
2、研究了
startEngine
发现,它在里面判断JSContext
是否为空,如果为空,则new
,并且context监听po
、bt
、_OC_defineClass
等JS
方法,如果不为空则return返回。接下来先看_OC_defineClass
image.png
-
2.1、在
startEngine
函数底部发现,加载了JSPatch.js
文件
image.png
- 3、学习上面JSContext的同学应该发现,这个
_OC_defineClass
跟demo.js
的defineClass
有点类似,理论这两个方法名应该是一致的
image.png
- 4、翻阅查询
__OC__
,在JSPatch.js
文件中发现,发现其实defineClass
并没有直接调用JSContext,而是调用到JSPatch.js
中的全局函数defineClass
,函数中会调用JSContext
中的_OC_defineClass
image.png
![](https://img.haomeiwen.com/i976255/88bf22bd548ef0d7.png)
- 4.1、如上图,
defineClass
函数内部调用了_formatDefineMethods
函数,可以发现,其实是取出对象方法,并且把方法实现,重新包装一个数组里面有两个值,一个是参数的个数,第二个是一个函数的实现
image.png
- 5、接下来回调到
OC
中的defineClass
,传进这个三个参数,类名
、[对象方法]
、[类方法]
,并返回一个字典。这个字典会返回给JS,具体干什么的。。。。。
image.png
-
6、首先获取类对象
image.png
-
7、for循环两次遍历,第一次遍历对象方法,第二次遍历类方法。
主要取出这个类对象、对象方法,然后调用
overrideMethod
方法
![](https://img.haomeiwen.com/i976255/ec9091eddd362432.png)
三、总结分析
替换、添加方法流程
-
1、在
didFinishLaunchingWithOptions
中启动了[JPEngine startEngine]
,执行了JSPatch.js
image.png
-
2、在
JSPatch.js
中,利用Object.defineProperty
方法,给Object
对象添加了_customMethods
属性里面定义的__c
、super
、performSelectorInOC
、performSelector
4个方法
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、利用
methodSignatureForSelector
和forwardInvocation
做消息转发的简单案例
image.png
image.png
-
8、
overrideMethod
函数步骤。- a、把
forwardInvocation
的实现替换成了JPForwardInvocation
image.png
- b、如果有实现
handleBtn:
的话,就添加方法ORIGhandleBtn:
image.png
- c、把方法
hanleBtn:
实现,直接转发进入消息转发流程,由于forwardInvocation
被修改为JPForwardInvocation
,所以就走JPForwardInvocation
的流程
image.png
- a、把
触发按钮事件的流程
-
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
![](https://img.haomeiwen.com/i976255/6e96faed60c8f18f.png)
转换后的函数
![](https://img.haomeiwen.com/i976255/eda96c0d29b466f7.png)
-
6、由于之前通完
Object.defineProperty
给Object
添加过_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个步骤
网友评论