最近在搗鼓跨平臺(tái)開(kāi)發(fā),最終選擇使用React Native.這對(duì)于不太了解前端的原生開(kāi)發(fā)者來(lái)說(shuō),坑還是挺多的.
今天我們來(lái)了解下RN的通信機(jī)制
react-native用iOS自帶的JavaScriptCore作為JS的解析引擎,但并沒(méi)有用到JavaScriptCore提供的一些可以讓JS與OC互調(diào)的特性,而是實(shí)現(xiàn)了一套機(jī)制,這套機(jī)制可以通用在所有的JS引擎上,在沒(méi)有JavaScriptCore的情況下,也可以用webview代替,實(shí)際上項(xiàng)目匯總已經(jīng)有用webview作為引擎的實(shí)現(xiàn),應(yīng)該是用于兼容ios7以下沒(méi)有JavaScriptCore的版本
普通的JS-OC通信很簡(jiǎn)單,OC向JS傳信息有現(xiàn)成的接口,比如webview提供的stringByEvaluatingJavaScriptFromString方法可以直接在當(dāng)前的context上執(zhí)行一段JS腳本,并且可以獲取執(zhí)行后的返回值,這個(gè)返回值就相當(dāng)于JS向OC傳遞信息.react-native也是以此為基礎(chǔ),通過(guò)各種手段,實(shí)現(xiàn)了在OC定義一個(gè)模塊方法,JS可以直接調(diào)用這個(gè)模塊方法并還可以無(wú)縫銜接回調(diào)
模塊配置表
首先OC要 告訴JS她有什么模塊,模塊里面有什么方法,JS才知道有這些方法后才有可能去調(diào)用這些方法,這里的實(shí)現(xiàn)是OC生成一分模塊配置表傳給JS,配置表里包括了所有模塊和模塊里方法的信息,例如:
OC端和JS端分別有一個(gè)bridge,兩個(gè)bridge都保存了同樣一份模塊配置表,JS調(diào)用OC模塊方法時(shí),通過(guò)bridge里的配置表把模塊方法轉(zhuǎn)為模塊ID和方法ID傳給OC,OC通過(guò)bridge的模塊配置表找到對(duì)應(yīng)的方法執(zhí)行之,以上述代碼為例,流程大致是這樣:
在了解這個(gè)調(diào)用流程之前,我們先來(lái)看看OC的模塊配置表是怎么來(lái)的,我們?cè)谛陆ㄒ粋€(gè)OC模塊時(shí),JS和OC都不需要為新的模塊去手動(dòng)添加一些配置,模塊配置是自動(dòng)生成的,只要項(xiàng)目中有一個(gè)模塊,就會(huì)把這個(gè)模塊添加到配置表里,那這個(gè)模塊配置表示怎樣生成的呢?分以下兩個(gè)步驟:
1,取所有模塊類(lèi)
每個(gè)模塊類(lèi)都實(shí)現(xiàn)了RCTBridgeModule接口,可以通過(guò)runtime接口objc_getClassList或者objc_copyClassList取出項(xiàng)目里所有類(lèi),然后逐個(gè)判斷是否實(shí)現(xiàn)了RCTBridgeModule接口,就可以找到所有模塊類(lèi),實(shí)現(xiàn)在RCTBridgeModuleClassByModule()方法里.
2,取出模塊里暴露給JS的方法
一個(gè)模塊里可以有很多方法,一些是可以暴露給JS直接調(diào)用的,一些是私有的不想暴露給JS的,怎樣做到提取這些暴露的方法呢?我能想到的方法是對(duì)要暴露的方法名制定一些規(guī)則,比如用RCTExport作為前綴,然后用runtime方法class_getInstaceMethod取出所有方法名字,提取以RCTExport為前綴的方法,但是這樣做惡心的地方就是每個(gè)方法必須加前綴,react-native用了另一個(gè)黑魔法似的方法解決這個(gè)問(wèn)題:編譯屬性attribute;
在上述例子中我們看到模塊方法里有句代碼:RCT_EXPORT(),模塊里的方法加上這個(gè)宏就可以實(shí)現(xiàn)暴露給JS,無(wú)需其他規(guī)則,那這個(gè)宏做了什么呢?來(lái)看看他的定義:
這個(gè)宏的作用是用編譯屬性attribute給二進(jìn)制文件新建一個(gè)section,屬于__DATA數(shù)據(jù)段,名字為RCTExport,并在這個(gè)段里加入當(dāng)前方法名。編譯器在編譯時(shí)會(huì)找到attribute進(jìn)行處理,為生成的可執(zhí)行文件加入相應(yīng)的內(nèi)容。效果可以從linkmap看出來(lái):
可以看到可執(zhí)行文件數(shù)據(jù)段多了個(gè)RCTExport段,內(nèi)容就是各個(gè)要暴露給JS的方法,這些內(nèi)容是可以在運(yùn)行時(shí)獲取到的,在RCTBridge的RCTExportMethodByModuleID()方法里獲取這些內(nèi)容,提取每個(gè)方法的類(lèi)名和方法名,就完成了提取模塊里暴露給JS方法的工作.
整個(gè)模塊類(lèi)/方法提取實(shí)現(xiàn)在RCTRemoteModulesConfig()方法里
調(diào)用流程
接下來(lái)看看JS調(diào)用OC模塊方法的詳細(xì)流程,包括callBack回調(diào),這時(shí)需要細(xì)化一下上述的調(diào)用流程圖:
看起來(lái)有點(diǎn)復(fù)雜,從發(fā)起調(diào)用到執(zhí)行回調(diào)總共11個(gè)步驟,下面來(lái)說(shuō)明下:
1,JS端調(diào)用某個(gè)OC模塊暴露出來(lái)的方法
2,把上一步的調(diào)用分解為ModuleName,MethodName,arguments,扔給MessageQueue處理.在初始化時(shí)模塊配置表上的每一個(gè)模塊都生成了對(duì)應(yīng)的remoteModule對(duì)象,對(duì)象里也生成了跟模塊配置表里一一對(duì)應(yīng)的方法,這些方法里可以拿到自身的模塊名,方法名,并對(duì)callBack進(jìn)行一些處理,再移交給MessageQueue,具體實(shí)現(xiàn)在BatchedBridgeFactory.js的_createBridgeModule里.整個(gè)實(shí)現(xiàn)24行代碼.
3,在這一步把JS的callback函數(shù)緩存在MessageQueue的一個(gè)成員變量里,用CallbackID代表callback.再通過(guò)保存在MessageQueue的模塊配置表把上一步傳進(jìn)來(lái)的ModuleName和MethodName轉(zhuǎn)為ModuleID和MethodID;
4,把上述步驟得到的ModuleID,MethodID,callbackID和其他參數(shù)argus傳給OC.
5,OC接受到消息,通過(guò)模塊配置表拿到對(duì)應(yīng)的模塊和方法
6,RCTModuleMethod對(duì)JS傳過(guò)來(lái)的每一個(gè)參數(shù)進(jìn)行處理
7,OC模塊方法調(diào)用完畢,執(zhí)行block回調(diào)
8,調(diào)用到第六步說(shuō)明的RCTModuleMethod生成的block
9,block里帶著CallbackID和block傳過(guò)來(lái)的參數(shù)去調(diào)JS里MessageQueue的方法nvokeCallBackAndReturnFlushQueue
10,MessageQueue通過(guò)callbackID找到對(duì)應(yīng)的JScallback方法
11,調(diào)用callback方法,并把OC帶過(guò)來(lái)的參數(shù)一起傳遞過(guò)去,完成回調(diào)
整個(gè)流程概括為:
JS函數(shù)調(diào)用轉(zhuǎn)ModuleID/MethodID—>callback轉(zhuǎn)CallbackID—>OC根據(jù)ID拿到方法—>處理參數(shù)—>調(diào)用OC方法—>回調(diào)callbackID—>JS通過(guò)callbackID拿到callback執(zhí)行
事件響應(yīng)
上述第四步有一個(gè)問(wèn)題:JS是怎樣把數(shù)據(jù)傳給OC,讓OC去調(diào)用相應(yīng)方法?
答案是通過(guò)返回值:JS不會(huì)主動(dòng)傳遞數(shù)據(jù)給OC,在調(diào)用OC方法時(shí),會(huì)在上述第四步把ModuleID,MethodID等數(shù)據(jù)加到一個(gè)隊(duì)列里,等OC過(guò)來(lái)調(diào)JS的任意方法時(shí),再把這個(gè)隊(duì)列放回給OC,此時(shí)OC在執(zhí)行這個(gè)隊(duì)列里要調(diào)用的方法
總結(jié)
整個(gè)React Native的JS-OC通信機(jī)制大致就是這樣了,關(guān)鍵點(diǎn)在于:模塊化,模塊配置表,傳遞ID,封裝調(diào)用,事件響應(yīng),其設(shè)計(jì)思想和實(shí)現(xiàn)方法很值得學(xué)習(xí)借鑒。