溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

深入理解JavaScriptCore

發(fā)布時(shí)間:2020-08-10 08:37:19 來(lái)源:ITPUB博客 閱讀:172 作者:趙鈺瑩 欄目:web開(kāi)發(fā)

動(dòng)態(tài)化作為移動(dòng)客戶(hù)端技術(shù)的一個(gè)重要分支,一直是業(yè)界積極探索的方向。目前業(yè)界流行的動(dòng)態(tài)化方案,如Facebook的React Native,阿里巴巴的Weex都采用了前端系的DSL方案,而它們?cè)趇OS系統(tǒng)上能夠順利的運(yùn)行,都離不開(kāi)一個(gè)背后的功臣:JavaScriptCore(以下簡(jiǎn)稱(chēng)JSCore),它建立起了Objective-C(以下簡(jiǎn)稱(chēng)OC)和JavaScript(以下簡(jiǎn)稱(chēng)JS)兩門(mén)語(yǔ)言之間溝通的橋梁。無(wú)論是這些流行的動(dòng)態(tài)化方案,還是WebView Hybrid方案,亦或是之前廣泛流行的JSPatch,JSCore都在其中發(fā)揮了舉足輕重的作用。作為一名iOS開(kāi)發(fā)工程師,了解JSCore已經(jīng)逐漸成為了必備技能之一。

從瀏覽器談起

在iOS 7之后,JSCore作為一個(gè)系統(tǒng)級(jí)Framework被蘋(píng)果提供給開(kāi)發(fā)者。JSCore作為蘋(píng)果的瀏覽器引擎WebKit中重要組成部分,這個(gè)JS引擎已經(jīng)存在多年。如果想去追本溯源,探究JSCore的奧秘,那么就應(yīng)該從JS這門(mén)語(yǔ)言的誕生,以及它最重要的宿主-Safari瀏覽器開(kāi)始談起。

JavaScript歷史簡(jiǎn)介

JavaScript誕生于1995年,它的設(shè)計(jì)者是Netscape的Brendan Eich,而此時(shí)的Netscape正是瀏覽器市場(chǎng)的霸主。

而二十多年前,當(dāng)時(shí)人們?cè)跒g覽網(wǎng)頁(yè)的體驗(yàn)極差,因?yàn)槟菚?huì)兒的瀏覽器幾乎只有頁(yè)面的展示能力,沒(méi)有和用戶(hù)的交互邏輯處理能力。所以即使一個(gè)必填輸入框傳空,也需要經(jīng)過(guò)服務(wù)端驗(yàn)證,等到返回結(jié)果之后才給出響應(yīng),再加上當(dāng)時(shí)的網(wǎng)速很慢,可能半分鐘過(guò)去了,返回的結(jié)果是告訴你某個(gè)必填字段未填。所以Brendan花了十天寫(xiě)出了JavaScript,由瀏覽器解釋執(zhí)行,從此之后瀏覽器也有了一些基本的交互處理能力,以及表單數(shù)據(jù)驗(yàn)證能力。

而B(niǎo)rendan可能沒(méi)有想到,在二十多年后的今天。JS這門(mén)解釋執(zhí)行的動(dòng)態(tài)腳本語(yǔ)言,不光成為前端屆的“正統(tǒng)”,還入侵了后端開(kāi)發(fā)領(lǐng)域,在編程語(yǔ)言排行榜上進(jìn)入前三甲,僅次于Python和Java。而如何解釋執(zhí)行JS,則是各家引擎的核心技術(shù)。目前市面上比較常見(jiàn)的JS引擎有Google的V8(它被運(yùn)用在Android操作系統(tǒng)以及Google的Chrome上),以及我們今天的主角JSCore(它被運(yùn)用在iOS操作系統(tǒng)以及Safari上)。

WebKit

我們每天都會(huì)接觸瀏覽器,使用瀏覽器進(jìn)行工作、娛樂(lè)。讓瀏覽器能夠正常工作最核心的部分就是瀏覽器的內(nèi)核,每個(gè)瀏覽器都有自己的內(nèi)核,Safari的內(nèi)核就是WebKit。WebKit誕生于1998年,并于2005年由Apple公司開(kāi)源,Google的Blink也是在WebKit的分支上進(jìn)行開(kāi)發(fā)的。

WebKit由多個(gè)重要模塊組成,通過(guò)下圖我們可以對(duì)WebKit有個(gè)整體的了解:

深入理解JavaScriptCore 簡(jiǎn)單點(diǎn)講,WebKit就是一個(gè)頁(yè)面渲染以及邏輯處理引擎,前端工程師把HTML、JavaScript、CSS這“三駕馬車(chē)”作為輸入,經(jīng)過(guò)WebKit的處理,就輸出成了我們能看到以及操作的Web頁(yè)面。從上圖我們可以看出來(lái),WebKit由圖中框住的四個(gè)部分組成。而其中最主要的就是WebCore和JSCore(或者是其它JS引擎),這兩部分我們會(huì)分成兩個(gè)小章節(jié)詳細(xì)講述。除此之外,WebKit Embedding API是負(fù)責(zé)瀏覽器UI與WebKit進(jìn)行交互的部分,而WebKit Ports則是讓W(xué)ebkit更加方便的移植到各個(gè)操作系統(tǒng)、平臺(tái)上,提供的一些調(diào)用Native Library的接口,比如在渲染層面,在iOS系統(tǒng)中,Safari是交給CoreGraphics處理,而在Android系統(tǒng)中,Webkit則是交給Skia。

WebCore

在上面的WebKit組成圖中,我們可以發(fā)現(xiàn)只有WebCore是紅色的。這是因?yàn)闀r(shí)至今日,WebKit已經(jīng)有很多的分支以及各大廠家也進(jìn)行了很多優(yōu)化改造,唯獨(dú)WebCore這個(gè)部分是所有WebKit共享的。WebCore是WebKit中代碼最多的部分,也是整個(gè)WebKit中最核心的渲染引擎。那首先我們來(lái)看看整個(gè)WebKit的渲染流程:

深入理解JavaScriptCore


首先瀏覽器通過(guò)URL定位到了一堆由HTML、CSS、JS組成的資源文件,通過(guò)加載器(這個(gè)加載器的實(shí)現(xiàn)也很復(fù)雜,在此不多贅述)把資源文件給WebCore。之后HTML Parser會(huì)把HTML解析成DOM樹(shù),CSS Parser會(huì)把CSS解析成CSSOM樹(shù)。最后把這兩棵樹(shù)合并,生成最終需要的渲染樹(shù),再經(jīng)過(guò)布局,與具體WebKit Ports的渲染接口,把渲染樹(shù)渲染輸出到屏幕上,成為了最終呈現(xiàn)在用戶(hù)面前的Web頁(yè)面。

JSCore

概述

終于講到我們這期的主角——JSCore。JSCore是WebKit默認(rèn)內(nèi)嵌的JS引擎,之所以說(shuō)是默認(rèn)內(nèi)嵌,是因?yàn)楹芏嗷赪ebKit分支開(kāi)發(fā)的瀏覽器引擎都開(kāi)發(fā)了自家的JS引擎,其中最出名的就是Chrome的V8。這些JS引擎的使命都相同,那就是解釋執(zhí)行JS腳本。而從上面的渲染流程圖我們可以看到,JS和DOM樹(shù)之間存在著互相關(guān)聯(lián),這是因?yàn)闉g覽器中的JS腳本最主要的功能就是操作DOM樹(shù),并與之交互。同樣的,我們也通過(guò)一張圖看下它的工作流程:

深入理解JavaScriptCore

可以看到,相比靜態(tài)編譯語(yǔ)言生成語(yǔ)法樹(shù)之后,還需要進(jìn)行鏈接,裝載生成可執(zhí)行文件等操作,解釋型語(yǔ)言在流程上要簡(jiǎn)化很多。這張流程圖右邊畫(huà)框的部分就是JSCore的組成部分:Lexer、Parser、LLInt以及JIT的部分(之所以JIT的部分是用橙色標(biāo)注,是因?yàn)椴⒉皇撬械腏SCore中都有JIT部分)。接下來(lái)我們就搭配整個(gè)工作流程介紹每一部分,它主要分為以下三個(gè)部分:詞法分析、語(yǔ)法分析以及解釋執(zhí)行。

PS:嚴(yán)格的講,語(yǔ)言本身并不存在編譯型或者是解釋型,因?yàn)檎Z(yǔ)言只是一些抽象的定義與約束,并不要求具體的實(shí)現(xiàn),執(zhí)行方式。這里講JS是一門(mén)“解釋型語(yǔ)言”只是JS一般是被JS引擎動(dòng)態(tài)解釋執(zhí)行,而并不是語(yǔ)言本身的屬性。

詞法分析:Lexer

詞法分析很好理解,就是把一段我們寫(xiě)的源代碼分解成Token序列的過(guò)程,這一過(guò)程也叫分詞。在JSCore,詞法分析是由Lexer來(lái)完成(有的編譯器或者解釋器把分詞叫做Scanner)。

這是一句很簡(jiǎn)單的C語(yǔ)言表達(dá)式:

sum = 3 + 2;

將其標(biāo)記化之后可以得到下表的內(nèi)容:

深入理解JavaScriptCore

這就是詞法分析之后的結(jié)果,但是詞法分析并不會(huì)關(guān)注每個(gè)Token之間的關(guān)系,是否匹配,僅僅是把它們區(qū)分開(kāi)來(lái),等待語(yǔ)法分析來(lái)把這些Token“串起來(lái)”。詞法分析函數(shù)一般是由語(yǔ)法分析器(Parser)來(lái)進(jìn)行調(diào)用的。在JSCore中,詞法分析器Lexer的代碼主要集中在parser/Lexer.h、Lexer.cpp中。

語(yǔ)法分析:Parser

跟人類(lèi)語(yǔ)言一樣,我們講話(huà)的時(shí)候其實(shí)是按照約定俗成,交流習(xí)慣按照一定的語(yǔ)法講出一個(gè)又一個(gè)詞語(yǔ)。那類(lèi)比到計(jì)算機(jī)語(yǔ)言,計(jì)算機(jī)要理解一門(mén)計(jì)算機(jī)語(yǔ)言,也要理解一個(gè)語(yǔ)句的語(yǔ)法。例如以下一段JS語(yǔ)句:

var sum = 2 + 3;
var a = sum + 5;

Parser會(huì)把Lexer分析之后生成的token序列進(jìn)行語(yǔ)法分析,并生成對(duì)應(yīng)的一棵抽象語(yǔ)法樹(shù)(AST)。這個(gè)樹(shù)長(zhǎng)什么樣呢?在這里推薦一個(gè)網(wǎng)站: esprima Parser ,輸入JS語(yǔ)句可以立馬生成我們所需的AST。例如,以上語(yǔ)句就被生成這樣的一棵樹(shù):

深入理解JavaScriptCore

之后,ByteCodeGenerator會(huì)根據(jù)AST來(lái)生成JSCore的字節(jié)碼,完成整個(gè)語(yǔ)法解析步驟。

解釋執(zhí)行:LLInt和JIT

JS源代碼經(jīng)過(guò)了詞法分析和語(yǔ)法分析這兩個(gè)步驟,轉(zhuǎn)成了字節(jié)碼,其實(shí)就是經(jīng)過(guò)任何一門(mén)程序語(yǔ)言必經(jīng)的步驟--編譯。但是不同于我們編譯運(yùn)行OC代碼,JS編譯結(jié)束之后,并不會(huì)生成存放在內(nèi)存或者硬盤(pán)之中的目標(biāo)代碼或可執(zhí)行文件。生成的指令字節(jié)碼,會(huì)被立即被JSCore這臺(tái)虛擬機(jī)進(jìn)行逐行解釋執(zhí)行。

運(yùn)行指令字節(jié)碼(ByteCode)是JS引擎中很核心的部分,各家JS引擎的優(yōu)化也主要集中于此。JSByteCode的解釋執(zhí)行是一套很復(fù)雜的系統(tǒng),特別是加入了OSR和多級(jí)JIT技術(shù)之后,整個(gè)解釋執(zhí)行變的越來(lái)越高效,并且讓整個(gè)ByteCode的執(zhí)行在低延時(shí)之間和高吞吐之間有個(gè)很好的平衡:由低延時(shí)的LLInt來(lái)解釋執(zhí)行ByteCode,當(dāng)遇到多次重復(fù)調(diào)用或者是遞歸,循環(huán)等條件會(huì)通過(guò)OSR切換成JIT進(jìn)行解釋執(zhí)行(根據(jù)具體觸發(fā)條件會(huì)進(jìn)入不同的JIT進(jìn)行動(dòng)態(tài)解釋?zhuān)﹣?lái)加快速度。由于這部分內(nèi)容較為復(fù)雜,而且不是本文重點(diǎn),故只做簡(jiǎn)單介紹,不做深入的討論。

JSCore值得注意的Feature

除了以上部分,JSCore還有幾個(gè)值得注意的Feature。

基于寄存器的指令集結(jié)構(gòu)

JSCore采用的是基于寄存器的指令集結(jié)構(gòu),相比于基于棧的指令集結(jié)構(gòu)(比如有些JVM的實(shí)現(xiàn)),因?yàn)椴恍枰巡僮鹘Y(jié)果頻繁入棧出棧,所以這種架構(gòu)的指令集執(zhí)行效率更高。但是由于這樣的架構(gòu)也造成內(nèi)存開(kāi)銷(xiāo)更大的問(wèn)題,除此之外,還存在移植性弱的問(wèn)題,因?yàn)樘摂M機(jī)中的虛擬寄存器需要去匹配到真實(shí)機(jī)器中CPU的寄存器,可能會(huì)存在真實(shí)CPU寄存器不足的問(wèn)題。

基于寄存器的指令集結(jié)構(gòu)通常都是三地址或者二地址的指令集,例如:

i = a + b;
//轉(zhuǎn)成三地址指令:
add i,a,b; //把a(bǔ)寄存器中的值和b寄存器中的值相加,存入i寄存器

在三地址的指令集中的運(yùn)算過(guò)程是把a(bǔ)和b分別mov到兩個(gè)寄存器,然后把這兩個(gè)寄存器的值求和之后,存入第三個(gè)寄存器。這就是三地址指令運(yùn)算過(guò)程。

而基于棧的一般都是零地址指令集,因?yàn)樗倪\(yùn)算不依托于具體的寄存器,而是使用對(duì)操作數(shù)棧和具體運(yùn)算符來(lái)完成整個(gè)運(yùn)算。

單線(xiàn)程機(jī)制

值得注意的是,整個(gè)JS代碼是執(zhí)行在一條線(xiàn)程里的,它并不像我們使用的OC、Java等語(yǔ)言,在自己的執(zhí)行環(huán)境里就能申請(qǐng)多條線(xiàn)程去處理一些耗時(shí)任務(wù)來(lái)防止阻塞主線(xiàn)程。JS代碼本身并不存在多線(xiàn)程處理任務(wù)的能力。但是為什么JS也存在多線(xiàn)程異步呢?強(qiáng)大的事件驅(qū)動(dòng)機(jī)制,是讓JS也可以進(jìn)行多線(xiàn)程處理的關(guān)鍵。

事件驅(qū)動(dòng)機(jī)制

之前講到,JS的誕生就是為了讓瀏覽器也擁有一些交互,邏輯處理能力。而JS與瀏覽器之間的交互是通過(guò)事件來(lái)實(shí)現(xiàn)的,比如瀏覽器檢測(cè)到發(fā)生了用戶(hù)點(diǎn)擊,會(huì)傳遞一個(gè)點(diǎn)擊事件通知JS線(xiàn)程去處理這個(gè)事件。

那通過(guò)這一特性,我們可以讓JS也進(jìn)行異步編程,簡(jiǎn)單來(lái)講就是遇到耗時(shí)任務(wù)時(shí),JS可以把這個(gè)任務(wù)丟給一個(gè)由JS宿主提供的工作線(xiàn)程(WebWorker)去處理。等工作線(xiàn)程處理完之后,會(huì)發(fā)送一個(gè)message讓JS線(xiàn)程知道這個(gè)任務(wù)已經(jīng)被執(zhí)行完了,并在JS線(xiàn)程上去執(zhí)行相應(yīng)的事件處理程序。(但是需要注意,由于工作線(xiàn)程和JS線(xiàn)程并不在一個(gè)運(yùn)行環(huán)境,所以它們并不共享一個(gè)作用域,故工作線(xiàn)程也不能操作window和DOM。)

JS線(xiàn)程和工作線(xiàn)程,以及瀏覽器事件之間的通信機(jī)制叫做事件循環(huán)(EventLoop),類(lèi)似于iOS的runloop。它有兩個(gè)概念,一個(gè)是Call Stack,一個(gè)是Task Queue。當(dāng)工作線(xiàn)程完成異步任務(wù)之后,會(huì)把消息推到Task Queue,消息就是注冊(cè)時(shí)的回調(diào)函數(shù)。當(dāng)Call Stack為空的時(shí)候,主線(xiàn)程會(huì)從Task Queue里取一條消息放入Call Stack來(lái)執(zhí)行,JS主線(xiàn)程會(huì)一直重復(fù)這個(gè)動(dòng)作直到消息隊(duì)列為空。

深入理解JavaScriptCore

以上這張圖大概描述了JSCore的事件驅(qū)動(dòng)機(jī)制,整個(gè)JS程序其實(shí)就是這樣跑起來(lái)的。這個(gè)其實(shí)跟空閑狀態(tài)下的iOS Runloop有點(diǎn)像,當(dāng)基于Port的Source事件喚醒runloop之后,會(huì)去處理當(dāng)前隊(duì)列里的所有source事件。JS的事件驅(qū)動(dòng),跟消息隊(duì)列其實(shí)是“異曲同工”。也正因?yàn)楣ぷ骶€(xiàn)程和事件驅(qū)動(dòng)機(jī)制的存在,才讓JS有了多線(xiàn)程異步能力。

iOS中的JSCore

iOS7之后,蘋(píng)果對(duì)WebKit中的JSCore進(jìn)行了Objective-C的封裝,并提供給所有的iOS開(kāi)發(fā)者。JSCore框架給Swift、OC以及C語(yǔ)言編寫(xiě)的App提供了調(diào)用JS程序的能力。同時(shí)我們也可以使用JSCore往JS環(huán)境中去插入一些自定義對(duì)象。

iOS中可以使用JSCore的地方有多處,比如封裝在UIWebView中的JSCore,封裝在WKWebView中的JSCore,以及系統(tǒng)提供的JSCore。實(shí)際上,即使同為JSCore,它們之間也存在很多區(qū)別。因?yàn)殡S著JS這門(mén)語(yǔ)言的發(fā)展,JS的宿主越來(lái)越多,有各種各樣的瀏覽器,甚至是常見(jiàn)于服務(wù)端的Node.js(基于V8運(yùn)行)。隨時(shí)使用場(chǎng)景的不同,以及WebKit團(tuán)隊(duì)自身不停的優(yōu)化,JSCore逐漸分化出不同的版本。除了老版本的JSCore,還有2008年宣布的運(yùn)行在Safari、WKWebView中的Nitro(SquirrelFish)等等。而在本文中,我們主要介紹iOS系統(tǒng)自帶的JSCore Framework。

iOS官方文檔 對(duì)JSCore的介紹很簡(jiǎn)單,其實(shí)主要就是給App提供了調(diào)用JS腳本的能力。我們首先通過(guò)JSCore Framework的15個(gè)開(kāi)放頭文件來(lái)“管中窺豹”,如下圖所示:

深入理解JavaScriptCore

乍一看,概念很多。但是除去一些公共頭文件以及一些很細(xì)節(jié)的概念,其實(shí)真正常用的并不多,筆者認(rèn)為很有必要了解的概念只有4個(gè):JSVM、JSContext、JSValue、JSExport。鑒于講述這些概念的文章已經(jīng)有很多,本文盡量從一些不同的角度(比如原理,延伸對(duì)比等)去解釋這些概念。

JSVirtualMachine

一個(gè)JSVirtualMachine(以下簡(jiǎn)稱(chēng)JSVM)實(shí)例代表了一個(gè)自包含的JS運(yùn)行環(huán)境,或者是一系列JS運(yùn)行所需的資源。該類(lèi)有兩個(gè)主要的使用用途:一是支持并發(fā)的JS調(diào)用,二是管理JS和Native之間橋?qū)ο蟮膬?nèi)存。

JSVM是我們要學(xué)習(xí)的第一個(gè)概念。官方介紹JSVM為JavaScript的執(zhí)行提供底層資源,而從類(lèi)名直譯過(guò)來(lái),一個(gè)JSVM就代表一個(gè)JS虛擬機(jī),我們?cè)谏厦嬉蔡岬搅颂摂M機(jī)的概念,那我們先討論一下什么是虛擬機(jī)。首先我們可以看看(可能是)最出名的虛擬機(jī)——JVM(Java虛擬機(jī)),JVM主要做兩個(gè)事情:

  1. 首先它要做的是把JavaC編譯器生成的ByteCode(ByteCode其實(shí)就是JVM的虛擬機(jī)器指令)生成每臺(tái)機(jī)器所需要的機(jī)器指令,讓Java程序可執(zhí)行(如下圖)。

  2. 第二步,JVM負(fù)責(zé)整個(gè)Java程序運(yùn)行時(shí)所需要的內(nèi)存空間管理、GC以及Java程序與Native(即C,C++)之間的接口等等。

深入理解JavaScriptCore

從功能上來(lái)看,一個(gè)高級(jí)語(yǔ)言虛擬機(jī)主要分為兩部分,一個(gè)是解釋器部分,用來(lái)運(yùn)行高級(jí)語(yǔ)言編譯生成的ByteCode,還有一部分則是Runtime運(yùn)行時(shí),用來(lái)負(fù)責(zé)運(yùn)行時(shí)的內(nèi)存空間開(kāi)辟、管理等等。實(shí)際上,JSCore常常被認(rèn)為是一個(gè)JS語(yǔ)言的優(yōu)化虛擬機(jī),它做著JVM類(lèi)似的事情,只是相比靜態(tài)編譯的Java,它還多承擔(dān)了把JS源代碼編譯成字節(jié)碼的工作。

既然JSCore被認(rèn)為是一個(gè)虛擬機(jī),那JSVM又是什么?實(shí)際上,JSVM就是一個(gè)抽象的JS虛擬機(jī),讓開(kāi)發(fā)者可以直接操作。在App中,我們可以運(yùn)行多個(gè)JSVM來(lái)執(zhí)行不同的任務(wù)。而且每一個(gè)JSContext(下節(jié)介紹)都從屬于一個(gè)JSVM。但是需要注意的是每個(gè)JSVM都有自己獨(dú)立的堆空間,GC也只能處理JSVM內(nèi)部的對(duì)象(在下節(jié)會(huì)簡(jiǎn)單講解JS的GC機(jī)制)。所以說(shuō),不同的JSVM之間是無(wú)法傳遞值的。

值得注意的還有,在上面的章節(jié)中,我們提到的JS單線(xiàn)程機(jī)制。這意味著,在一個(gè)JSVM中,只有一條線(xiàn)程可以跑JS代碼,所以我們無(wú)法使用JSVM進(jìn)行多線(xiàn)程處理JS任務(wù)。如果我們需要多線(xiàn)程處理JS任務(wù)的場(chǎng)景,就需要同時(shí)生成多個(gè)JSVM,從而達(dá)到多線(xiàn)程處理的目的。

JS的GC機(jī)制

JS同樣也不需要我們?nèi)ナ謩?dòng)管理內(nèi)存。JS的內(nèi)存管理使用的是GC機(jī)制(Tracing Garbage Collection)。不同于OC的引用計(jì)數(shù),Tracing Garbage Collection是由GCRoot(Context)開(kāi)始維護(hù)的一條引用鏈,一旦引用鏈無(wú)法觸達(dá)某對(duì)象節(jié)點(diǎn),這個(gè)對(duì)象就會(huì)被回收掉。如下圖所示:

深入理解JavaScriptCore

JSContext

一個(gè)JSContext表示了一次JS的執(zhí)行環(huán)境。我們可以通過(guò)創(chuàng)建一個(gè)JSContext去調(diào)用JS腳本,訪(fǎng)問(wèn)一些JS定義的值和函數(shù),同時(shí)也提供了讓JS訪(fǎng)問(wèn)Native對(duì)象,方法的接口。

JSContext是我們?cè)趯?shí)際使用JSCore時(shí),經(jīng)常用到的概念之一。"Context"這個(gè)概念我們都或多或少的在其它開(kāi)發(fā)場(chǎng)景中見(jiàn)過(guò),它最常被翻譯成“上下文”。那什么是上下文?比如在一篇文章中,我們看到一句話(huà):“他飛快的跑了出去?!钡侨绻覀儾豢瓷舷挛牡脑?huà),我們并不知道這句話(huà)究竟是什么意思:誰(shuí)跑了出去?他是誰(shuí)?他為什么要跑?

寫(xiě)計(jì)算機(jī)理解的程序語(yǔ)言跟寫(xiě)文章是相似的,我們運(yùn)行任何一段語(yǔ)句都需要有這樣一個(gè)“上下文”的存在。比如之前外部變量的引入、全局變量、函數(shù)的定義、已經(jīng)分配的資源等等。有了這些信息,我們才能準(zhǔn)確的執(zhí)行每一句代碼。

同理,JSContext就是JS語(yǔ)言的執(zhí)行環(huán)境,所有JS代碼的執(zhí)行必須在一個(gè)JSContext之中,在WebView中也是一樣,我們可以通過(guò)KVC的方式獲取當(dāng)時(shí)WebView的JSContext。通過(guò)JSContext運(yùn)行一段JS代碼十分簡(jiǎn)單,如下面這個(gè)例子:

    JSContext *context = [[JSContext alloc] init];
    [context evaluateScript:@"var a = 1;var b = 2;"];
    NSInteger sum = [[context evaluateScript:@"a + b"] toInt32];//sum=3

借助evaluateScript API,我們就可以在OC中搭配JSContext執(zhí)行JS代碼。它的返回值是JS中最后生成的一個(gè)值,用屬于當(dāng)前JSContext中的JSValue(下一節(jié)會(huì)有介紹)包裹返回。

我們還可以通過(guò)KVC的方式,給JSContext塞進(jìn)去很多全局對(duì)象或者全局函數(shù):

    JSContext *context = [[JSContext alloc] init];
        context[@"globalFunc"] =  ^() {
        NSArray *args = [JSContext currentArguments];
        for (id obj in args) {
            NSLog(@"拿到了參數(shù):%@", obj);
        }
    };
    context[@"globalProp"] = @"全局變量字符串";
   [context evaluateScript:@"globalFunc(globalProp)"];//console輸出:“拿到了參數(shù):全局變量字符串”

這是一個(gè)很好用而且很重要的特性,有很多著名的借助JSCore的框架如JSPatch,都利用了這個(gè)特性去實(shí)現(xiàn)一些很巧妙的事情。在這里我們不過(guò)多探討可以利用它做什么,而是去研究它究竟是怎樣運(yùn)作的。在JSContext的API中,有一個(gè)值得注意的只讀屬性 -- JSValue類(lèi)型的globalObject。它返回當(dāng)前執(zhí)行JSContext的全局對(duì)象,例如在WebKit中,JSContext就會(huì)返回當(dāng)前的Window對(duì)象。

而這個(gè)全局對(duì)象其實(shí)也是JSContext最核心的東西,當(dāng)我們通過(guò)KVC方式與JSContext進(jìn)去取值賦值的時(shí)候,實(shí)際上都是在跟這個(gè)全局對(duì)象做交互,幾乎所有的東西都在全局對(duì)象里,可以說(shuō),JSContext只是globalObject的一層殼。對(duì)于上述兩個(gè)例子,本文取了context的globalObject,并轉(zhuǎn)成了OC對(duì)象,如下圖:

深入理解JavaScriptCore

可以看到這個(gè)globalObject保存了所有的變量與函數(shù),這更加印證了上文的說(shuō)法(至于為什么globalObject對(duì)應(yīng)OC對(duì)象是NSDictionary類(lèi)型,我們將在下節(jié)中講述)。所以我們還能得出另外一個(gè)結(jié)論,JS中所謂的全局變量,全局函數(shù)不過(guò)是全局對(duì)象的屬性和函數(shù)。

同時(shí)值得注意的是,每個(gè)JSContext都從屬于一個(gè)JSVM。我們可以通過(guò)JSContext的只讀屬性virtualMachine獲得當(dāng)前JSContext綁定的JSVM。JSContext和JSVM是多對(duì)一的關(guān)系,一個(gè)JSContext只能綁定一個(gè)JSVM,但是一個(gè)JSVM可以同時(shí)持有多個(gè)JSContext。而上文中我們提到,每個(gè)JSVM同時(shí)只有整個(gè)一個(gè)線(xiàn)程來(lái)執(zhí)行JS代碼,所以綜合來(lái)看,一次簡(jiǎn)單的通過(guò)JSCore運(yùn)行JS代碼,并在Native層獲取返回值的過(guò)程大致如下:

深入理解JavaScriptCore

JSValue

JSValue實(shí)例是一個(gè)指向JS值的引用指針。我們可以使用JSValue類(lèi),在OC和JS的基礎(chǔ)數(shù)據(jù)類(lèi)型之間相互轉(zhuǎn)換。同時(shí)我們也可以使用這個(gè)類(lèi),去創(chuàng)建包裝了Native自定義類(lèi)的JS對(duì)象,或者是那些由Native方法或者Block提供實(shí)現(xiàn)JS方法的JS對(duì)象。

在JSContext一節(jié)中,我們接觸了大量的JSValue類(lèi)型的變量。在JSContext一節(jié)中我們了解到,我們可以很簡(jiǎn)單的通過(guò)KVC操作JS全局對(duì)象,也可以直接獲得JS代碼執(zhí)行結(jié)果的返回值(同時(shí)每一個(gè)JS中的值都存在于一個(gè)執(zhí)行環(huán)境之中,也就是說(shuō)每個(gè)JSValue都存在于一個(gè)JSContext之中,這也就是JSValue的作用域),都是因?yàn)镴SCore幫我們用JSValue在底層自動(dòng)做了OC和JS的類(lèi)型轉(zhuǎn)換。

JSCore一共提供了如下10種類(lèi)型互換:

   Objective-C type  |   JavaScript type
 --------------------+---------------------
         nil         |     undefined
        NSNull       |        null
       NSString      |       string
       NSNumber      |   number, boolean
     NSDictionary    |   Object object
       NSArray       |    Array object
        NSDate       |     Date object
       NSBlock            |   Function object 
          id         |   Wrapper object 
        Class        | Constructor object

同時(shí)還提供了對(duì)應(yīng)的互換API(節(jié)選):

+ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context;
+ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context;
- (NSArray *)toArray;
- (NSDictionary *)toDictionary;

在講類(lèi)型轉(zhuǎn)換前,我們先了解一下JS這門(mén)語(yǔ)言的變量類(lèi)型。根據(jù)ECMAScript(可以理解為JS的標(biāo)準(zhǔn))的定義:JS中存在兩種數(shù)據(jù)類(lèi)型的值,一種是基本類(lèi)型值,它指的是簡(jiǎn)單的數(shù)據(jù)段。第二種是引用類(lèi)型值,指那些可能由多個(gè)值構(gòu)成的對(duì)象?;绢?lèi)型值包括"undefined","nul","Boolean","Number","String"(是的,String也是基礎(chǔ)類(lèi)型),除此之外都是引用類(lèi)型。對(duì)于前五種基礎(chǔ)類(lèi)型的互換,應(yīng)該沒(méi)有太多要講的。接下來(lái)會(huì)重點(diǎn)講講引用類(lèi)型的互換:

NSDictionary <--> Object

在上節(jié)中,我們把JSContext的globalObject轉(zhuǎn)換成OC對(duì)象,發(fā)現(xiàn)是NSDictionary類(lèi)型。要搞清楚這個(gè)轉(zhuǎn)換,首先我們對(duì)JS這門(mén)語(yǔ)言面向?qū)ο蟮奶匦赃M(jìn)行一個(gè)簡(jiǎn)單的了解。在JS中,對(duì)象就是一個(gè)引用類(lèi)型的實(shí)例。與我們熟悉的OC、Java不一樣,對(duì)象并不是一個(gè)類(lèi)的實(shí)例,因?yàn)樵贘S中并不存在類(lèi)的概念。ECMA把對(duì)象定義為:無(wú)序?qū)傩缘募?,其屬性可以包含基本值、?duì)象或者函數(shù)。從這個(gè)定義我們可以發(fā)現(xiàn),JS中的對(duì)象就是無(wú)序的鍵值對(duì),這和OC中的NSDictionary,Java中的HashMap何其相似。

    var person = { name: "Nicholas",age: 17};//JS中的person對(duì)象
    NSDictionary *person = @{@"name":@"Nicholas",@"age":@17};//OC中的person dictionary

在上面的實(shí)例代碼中,筆者使用了類(lèi)似的方式創(chuàng)建了JS中的對(duì)象(在JS中叫“對(duì)象字面量”表示法)與OC中的NSDictionary,相信可以更有助理解這兩個(gè)轉(zhuǎn)換。

NSBlock <--> Function Object

在上節(jié)的例子中,筆者在JSContext賦值了一個(gè)"globalFunc"的Block,并可以在JS代碼中當(dāng)成一個(gè)函數(shù)直接調(diào)用。我還可以使用"typeof"關(guān)鍵字來(lái)判斷globalFunc在JS中的類(lèi)型:

    NSString *type = [[context evaluateScript:@"typeof globalFunc"] toString];//type的值為"function"

通過(guò)這個(gè)例子,我們也能發(fā)現(xiàn)傳入的Block對(duì)象在JS中已經(jīng)被轉(zhuǎn)成了"function"類(lèi)型。"Function Object"這個(gè)概念對(duì)于我們寫(xiě)慣傳統(tǒng)面向?qū)ο笳Z(yǔ)言的開(kāi)發(fā)者來(lái)說(shuō),可能會(huì)比較晦澀。而實(shí)際上,JS這門(mén)語(yǔ)言,除了基本類(lèi)型以外,就是引用類(lèi)型。函數(shù)實(shí)際上也是一個(gè)"Function"類(lèi)型的對(duì)象,每個(gè)函數(shù)名實(shí)則是指向一個(gè)函數(shù)對(duì)象的引用。比如我們可以這樣在JS中定義一個(gè)函數(shù):

    var sum = function(num1,num2){
        return num1 + num2; 
    }

同時(shí)我們還可以這樣定義一個(gè)函數(shù)(不推薦):

    var sum = new Function("num1","num2","return num1 + num2");

按照第二種寫(xiě)法,我們就能很直觀的理解到函數(shù)也是對(duì)象,它的構(gòu)造函數(shù)就是Function,函數(shù)名只是指向這個(gè)對(duì)象的指針。而NSBlock是一個(gè)包裹了函數(shù)指針的類(lèi),JSCore把Function Object轉(zhuǎn)成NSBlock對(duì)象,可以說(shuō)是很合適的。

JSExport

實(shí)現(xiàn)JSExport協(xié)議可以開(kāi)放OC類(lèi)和它們的實(shí)例方法,類(lèi)方法,以及屬性給JS調(diào)用。

除了上一節(jié)提到的幾種特殊類(lèi)型的轉(zhuǎn)換,我們還剩下NSDate類(lèi)型,與id、class類(lèi)型的轉(zhuǎn)換需要弄清楚。而NSDate類(lèi)型無(wú)需贅述,所以我們?cè)谶@一節(jié)重點(diǎn)要弄清楚后兩者的轉(zhuǎn)換。

而通常情況下,我們?nèi)绻朐贘S環(huán)境中使用OC中的類(lèi)和對(duì)象,需要它們實(shí)現(xiàn)JSExport協(xié)議,來(lái)確定暴露給JS環(huán)境中的屬性和方法。比如我們需要向JS環(huán)境中暴露一個(gè)Person的類(lèi)與獲取名字的方法:

@protocol PersonProtocol <JSExport>
- (NSString *)fullName;//fullName用來(lái)拼接firstName和lastName,并返回全名
@end
@interface JSExportPerson : NSObject <PersonProtocol>
- (NSString *)sayFullName;//sayFullName方法
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@end

然后,我們可以把一個(gè)JSExportPerson的一個(gè)實(shí)例傳入JSContext,并且可以直接執(zhí)行fullName方法:

    JSExportPerson *person = [[JSExportPerson alloc] init];
    context[@"person"] = person;
    person.firstName = @"Di";
    person.lastName =@"Tang";
    [context evaluateScript:@"log(person.fullName())"];//調(diào)Native方法,打印出person實(shí)例的全名
    [context evaluateScript:@"person.sayFullName())"];//提示TypeError,'person.sayFullName' is undefined

這就是一個(gè)很簡(jiǎn)單的使用JSExport的例子,但請(qǐng)注意,我們只能調(diào)用在該對(duì)象在JSExport中開(kāi)放出去的方法,如果并未開(kāi)放出去,如上例中的"sayFullName"方法,直接調(diào)用則會(huì)報(bào)TypeError錯(cuò)誤,因?yàn)樵摲椒ㄔ贘S環(huán)境中并未被定義。

講完JSExport的具體使用方法,我們來(lái)看看我們最開(kāi)始的問(wèn)題。當(dāng)一個(gè)OC對(duì)象傳入JS環(huán)境之后,會(huì)轉(zhuǎn)成一個(gè)JSWrapperObject。那問(wèn)題來(lái)了,什么是JSWrapperObject?在JSCore的源碼中,我們可以找到一些線(xiàn)索。首先在JSCore的JSValue中,我們可以發(fā)現(xiàn)這樣一個(gè)方法:

@method
@abstract Create a JSValue by converting an Objective-C object.
@discussion The resulting JSValue retains the provided Objective-C object.
@param value The Objective-C object to be converted.
@result The new JSValue.
*/
+ (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context;

這個(gè)API可以傳入任意一個(gè)類(lèi)型的OC對(duì)象,然后返回一個(gè)持有該OC對(duì)象的JSValue。那這個(gè)過(guò)程肯定涉及到OC對(duì)象到JS對(duì)象的互換,所以我們只要分析一下這個(gè)方法的源碼(基于 這個(gè)分支 進(jìn)行分析)。由于源碼實(shí)現(xiàn)過(guò)長(zhǎng),我們只需要關(guān)注核心代碼,在JSContext中有一個(gè)"wrapperForObjCObject"方法,而實(shí)際上它又是調(diào)用了JSWrapperMap的"jsWrapperForObject"方法,這個(gè)方法就可以解答所有的疑惑:

//接受一個(gè)入?yún)bject,并返回一個(gè)JSValue
- (JSValue *)jsWrapperForObject:(id)object
{
    //對(duì)于每個(gè)對(duì)象,有專(zhuān)門(mén)的jsWrapper
    JSC::JSObject* jsWrapper = m_cachedJSWrappers.get(object);
    if (jsWrapper)
        return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:m_context];
    JSValue *wrapper;
    //如果該對(duì)象是個(gè)類(lèi)對(duì)象,則會(huì)直接拿到classInfo的constructor為實(shí)際的Value
    if (class_isMetaClass(object_getClass(object)))
        wrapper = [[self classInfoForClass:(Class)object] constructor];
    else {
        //對(duì)于普通的實(shí)例對(duì)象,由對(duì)應(yīng)的classInfo負(fù)責(zé)生成相應(yīng)JSWrappper同時(shí)retain對(duì)應(yīng)的OC對(duì)象,并設(shè)置相應(yīng)的Prototype
        JSObjCClassInfo* classInfo = [self classInfoForClass:[object class]];
        wrapper = [classInfo wrapperForObject:object];
    }
    JSC::ExecState* exec = toJS([m_context JSGlobalContextRef]);
    //將wrapper的值寫(xiě)入JS環(huán)境
    jsWrapper = toJS(exec, valueInternalValue(wrapper)).toObject(exec);
    //緩存object的wrapper對(duì)象
    m_cachedJSWrappers.set(object, jsWrapper);
    return wrapper;
}

在我們創(chuàng)建"JSWrapperObject"的對(duì)象過(guò)程中,我們會(huì)通過(guò)JSWrapperMap來(lái)為每個(gè)傳入的對(duì)象創(chuàng)建對(duì)應(yīng)的JSObjCClassInfo。這是一個(gè)非常重要的類(lèi),它有這個(gè)類(lèi)對(duì)應(yīng)JS對(duì)象的原型(Prototype)與構(gòu)造函數(shù)(Constructor)。然后由JSObjCClassInfo去生成具體OC對(duì)象的JSWrapper對(duì)象,這個(gè)JSWrapper對(duì)象中就有一個(gè)JS對(duì)象所需要的所有信息(即Prototype和Constructor)以及對(duì)應(yīng)OC對(duì)象的指針。之后,把這個(gè)jsWrapper對(duì)象寫(xiě)入JS環(huán)境中,即可在JS環(huán)境中使用這個(gè)對(duì)象了。

這也就是"JSWrapperObject"的真面目。而我們上文中提到,如果傳入的是類(lèi),那么在JS環(huán)境中會(huì)生成constructor對(duì)象,那么這點(diǎn)也很容易從源碼中看到,當(dāng)檢測(cè)到傳入的是類(lèi)的時(shí)候(類(lèi)本身也是個(gè)對(duì)象),則會(huì)直接返回constructor屬性,這也就是"constructor object"的真面目,實(shí)際上就是一個(gè)構(gòu)造函數(shù)。

那現(xiàn)在還有兩個(gè)問(wèn)題,第一個(gè)問(wèn)題是,OC對(duì)象有自己的繼承關(guān)系,那么在JS環(huán)境中如何描述這個(gè)繼承關(guān)系?第二個(gè)問(wèn)題是,JSExport的方法和屬性,又是如何讓JS環(huán)境中調(diào)用的呢?

我們先看第一個(gè)問(wèn)題,繼承關(guān)系要如何解決?在JS中,繼承是通過(guò)原型鏈來(lái)實(shí)現(xiàn),那什么是原型呢?原型對(duì)象是一個(gè)普通對(duì)象,而且就是構(gòu)造函數(shù)的一個(gè)實(shí)例。所有通過(guò)該構(gòu)造函數(shù)生成的對(duì)象都共享這一個(gè)對(duì)象,當(dāng)查找某個(gè)對(duì)象的屬性值,結(jié)果不存在時(shí),這時(shí)就會(huì)去對(duì)象的原型對(duì)象繼續(xù)找尋,是否存在該屬性,這樣就達(dá)到了一個(gè)封裝的目的。我們通過(guò)一個(gè)Person原型對(duì)象快速了解:

//原型對(duì)象是一個(gè)普通對(duì)象,而且就是Person構(gòu)造函數(shù)的一個(gè)實(shí)例。所有Person構(gòu)造函數(shù)的實(shí)例都共享這一個(gè)原型對(duì)象。
Person.prototype = {
   name:  'tony stark',
   age: 48,
   job: 'Iron Man',
   sayName: function() {
     alert(this.name);
   }
}

而原型鏈就是JS中實(shí)現(xiàn)繼承的關(guān)鍵,它的本質(zhì)就是重寫(xiě)構(gòu)造函數(shù)的原型對(duì)象,鏈接另一個(gè)構(gòu)造函數(shù)的原型對(duì)象。這樣查找某個(gè)對(duì)象的屬性,會(huì)沿著這條原型鏈一直查找下去,從而達(dá)到繼承的目的。我們通過(guò)一個(gè)例子快速了解一下:

    function mammal (){}
     mammal.prototype.commonness = function(){
           alert('哺乳動(dòng)物都用肺呼吸');
     }; 
    function Person() {}
    Person.prototype = new mammal();//原型鏈的生成,Person的實(shí)例也可以訪(fǎng)問(wèn)commonness屬性了
    Person.prototype.name = 'tony stark';
    Person.prototype.age  = 48;
    Person.prototype.job  = 'Iron Man';
    Person.prototype.sayName = function() {
          alert(this.name);
    }
    var person1 = new Person();
    person1.commonness(); // 彈出'哺乳動(dòng)物都用肺呼吸'
    person1.sayName(); // 'tony stark'

而我們?cè)谏蓪?duì)象的classinfo的時(shí)候(具體代碼見(jiàn)"allocateConstructorAndPrototypeWithSuperClassInfo"),還會(huì)生成父類(lèi)的classInfo。對(duì)每個(gè)實(shí)現(xiàn)過(guò)JSExport的OC類(lèi),JSContext里都會(huì)提供一個(gè)prototype。比如NSObject類(lèi),在JS里面就會(huì)有對(duì)應(yīng)的Object Prototype。對(duì)于其它的OC類(lèi),會(huì)創(chuàng)建對(duì)應(yīng)的Prototype,這個(gè)prototype的內(nèi)部屬性[Prototype]會(huì)指向?yàn)檫@個(gè)OC類(lèi)的父類(lèi)創(chuàng)建的Prototype。這個(gè)JS原型鏈就能反應(yīng)出對(duì)應(yīng)OC類(lèi)的繼承關(guān)系,在上例中,Person.prototype被賦值為一個(gè)mammal的實(shí)例對(duì)象,即原型的鏈接過(guò)程。

講完第一個(gè)問(wèn)題,我們?cè)賮?lái)看看第二個(gè)問(wèn)題。那JSExport是如何暴露OC方法到JS環(huán)境的呢?這個(gè)問(wèn)題的答案同樣出現(xiàn)在我們生成對(duì)象的classInfo的時(shí)候:

        Protocol *exportProtocol = getJSExportProtocol();
        forEachProtocolImplementingProtocol(m_class, exportProtocol, ^(Protocol *protocol){
            copyPrototypeProperties(m_context, m_class, protocol, prototype);
            copyMethodsToObject(m_context, m_class, protocol, NO, constructor);
        });

對(duì)于每個(gè)聲明在JSExport里的屬性和方法,classInfo會(huì)在prototype和constructor里面存入對(duì)應(yīng)的property和method。之后我們就可以通過(guò)具體的methodName和PropertyName生成的setter和getter方法,來(lái)獲取實(shí)際的SEL。最后就可以讓JSExport中的方法和屬性得到正確的訪(fǎng)問(wèn)。所以簡(jiǎn)單點(diǎn)講,JSExport就是負(fù)責(zé)把這些方法打個(gè)標(biāo),以methodName為key,SEL為value,存入一個(gè)map(prototype和constructor本質(zhì)上就是一個(gè)Map)中去,之后就可以通過(guò)methodName拿到對(duì)應(yīng)的SEL進(jìn)行調(diào)用。這也就解釋了上例中,我們調(diào)用一個(gè)沒(méi)有在JSExport中開(kāi)放的方法會(huì)顯示undefined,因?yàn)樯傻膶?duì)象里根本沒(méi)有這個(gè)key。

總結(jié)

JSCore給iOS App提供了JS可以解釋執(zhí)行的運(yùn)行環(huán)境與資源。對(duì)于我們實(shí)際開(kāi)發(fā)而言,最主要的就是JSContext和JSValue這兩個(gè)類(lèi)。JSContext提供互相調(diào)用的接口,JSValue為這個(gè)互相調(diào)用提供數(shù)據(jù)類(lèi)型的橋接轉(zhuǎn)換。讓JS可以執(zhí)行Native方法,并讓Native回調(diào)JS,反之亦然。

深入理解JavaScriptCore

利用JSCore,我們可以做很多有想象空間的事。所有基于JSCore的Hybrid開(kāi)發(fā)基本就是靠上圖的原理來(lái)實(shí)現(xiàn)互相調(diào)用,區(qū)別只是具體的實(shí)現(xiàn)方式和用途不大相同。大道至簡(jiǎn),只要正確理解這個(gè)基本流程,其它的所有方案不過(guò)是一些變通,都可以很快掌握。

一些引申閱讀

JSPatch的對(duì)象和方法沒(méi)有實(shí)現(xiàn)JSExport協(xié)議,JS是如何調(diào)OC方法的?

JS調(diào)OC并不是通過(guò)JSExport。通過(guò)JSExport實(shí)現(xiàn)的方式有諸多問(wèn)題,我們需要先寫(xiě)好Native的類(lèi),并實(shí)現(xiàn)JSExport協(xié)議,這個(gè)本身就不能滿(mǎn)足“Patch”的需求。

所以JSPatch另辟蹊徑,使用了OC的Runtime消息轉(zhuǎn)發(fā)機(jī)制做這個(gè)事情,如下面這一個(gè)簡(jiǎn)單的JSPatch調(diào)用代碼:

require('UIView') 
var view = UIView.alloc().init()
  1. require在全局作用域里生成UIView變量,來(lái)表示這個(gè)對(duì)象是一個(gè)OCClass。

  2. 通過(guò)正則把.alloc()改成._c('alloc'),來(lái)進(jìn)行方法收口,最終會(huì)調(diào)用_methodFunc()把類(lèi)名、對(duì)象、MethodName通過(guò)在Context早已定義好的Native方法,傳給OC環(huán)境。

  3. 最終調(diào)用OC的CallSelector方法,底層通過(guò)從JS環(huán)境拿到的類(lèi)名、方法名、對(duì)象之后,通過(guò)NSInvocation實(shí)現(xiàn)動(dòng)態(tài)調(diào)用。

JSPatch的通信并沒(méi)有通過(guò)JSExport協(xié)議,而是借助JSCore的Context與JSCore的類(lèi)型轉(zhuǎn)換和OC的消息轉(zhuǎn)發(fā)機(jī)制來(lái)完成動(dòng)態(tài)調(diào)用,實(shí)現(xiàn)思路真的很巧妙。

橋方法的實(shí)現(xiàn)是怎么通過(guò)JSCore交互的?

市面上常見(jiàn)的橋方法調(diào)用有兩種:

  1. 通過(guò)UIWebView的delegate方法:shouldStartLoadWithRequest來(lái)處理橋接JS請(qǐng)求。JSRequest會(huì)帶上methodName,通過(guò)WebViewBridge類(lèi)調(diào)用該method。執(zhí)行完之后,會(huì)使用WebView來(lái)執(zhí)行JS的回調(diào)方法,當(dāng)然實(shí)際上也是調(diào)用的WebView中的JSContext來(lái)執(zhí)行JS,完成整個(gè)調(diào)用回調(diào)流程。

  2. 通過(guò)UIWebView的delegate方法:在webViewDidFinishLoadwebViewDidFinishLoad里通過(guò)KVC的方式獲取UIWebView的JSContext,然后通過(guò)這個(gè)JSContext設(shè)置已經(jīng)準(zhǔn)備好的橋方法供JS環(huán)境調(diào)用。

作者簡(jiǎn)介

唐笛,美團(tuán)點(diǎn)評(píng)高級(jí)工程師。2017年加入原美團(tuán),目前作為外賣(mài)iOS團(tuán)隊(duì)主力開(kāi)發(fā),主要負(fù)責(zé)移動(dòng)端基礎(chǔ)設(shè)施建設(shè),動(dòng)態(tài)化等方向相關(guān)推進(jìn)工作,致力于提升移動(dòng)端研發(fā)效率與研發(fā)質(zhì)量。

【本文轉(zhuǎn)載自美團(tuán)技術(shù)團(tuán)隊(duì)微信公眾號(hào),原文鏈接:https://mp.weixin.qq.com/s/H5wBNAm93uPJDvCQCg0_cg】


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI