React Native通訊機制詳解

發表於2015-03-31

React Native是facebook剛開源的框架,可以用javascript直接開發原生APP,先不說這個框架後續是否能得到大眾認可,單從原始碼來說,這個框架原始碼裡有非常多的設計思想和實現方式值得學習,本篇先來看看它最基礎的JavaScript-ObjectC通訊機制(以下簡稱JS/OC)。

概覽

React Native用iOS自帶的JavaScriptCore作為JS的解析引擎,但並沒有用到JavaScriptCore提供的一些可以讓JS與OC互調的特性,而是自己實現了一套機制,這套機制可以通用於所有JS引擎上,在沒有JavaScriptCore的情況下也可以用webview代替,實際上專案裡就已經有了用webview作為解析引擎的實現,應該是用於相容iOS7以下沒有JavascriptCore的版本。

普通的JS-OC通訊實際上很簡單,OC向JS傳資訊有現成的介面,像webview提供的-stringByEvaluatingJavaScriptFromString方法可以直接在當前context上執行一段JS指令碼,並且可以獲取執行後的返回值,這個返回值就相當於JS向OC傳遞資訊。React Native也是以此為基礎,通過各種手段,實現了在OC定義一個模組方法,JS可以直接呼叫這個模組方法並還可以無縫銜接回撥。

舉個例子,OC定義了一個模組RCTSQLManager,裡面有個方法-query:successCallback:,JS可以直接呼叫RCTSQLManager.query並通過回撥獲取執行結果。:

接下來看看它是怎樣實現的。

模組配置表

首先OC要告訴JS它有什麼模組,模組裡有什麼方法,JS才知道有這些方法後才有可能去呼叫這些方法。這裡的實現是OC生成一份模組配置表傳給JS,配置表裡包括了所有模組和模組裡方法的資訊。例:

OC端和JS端分別各有一個bridge,兩個bridge都儲存了同樣一份模組配置表,JS呼叫OC模組方法時,通過bridge裡的配置表把模組方法轉為模組ID和方法ID傳給OC,OC通過bridge的模組配置表找到對應的方法執行之,以上述程式碼為例,流程大概是這樣(先不考慮callback):

在瞭解這個呼叫流程之前,我們先來看看OC的模組配置表式怎麼來的。我們在新建一個OC模組時,JS和OC都不需要為新的模組手動去某個地方新增一些配置,模組配置表是自動生成的,只要專案裡有一個模組,就會把這個模組加到配置表上,那這個模組配置表是怎樣自動生成的呢?分兩個步驟:

1.取所有模組類

每個模組類都實現了RCTBridgeModule介面,可以通過runtime介面objc_getClassList或objc_copyClassList取出專案裡所有類,然後逐個判斷是否實現了RCTBridgeModule介面,就可以找到所有模組類,實現在RCTBridgeModuleClassesByModuleID()方法裡。

2.取模組裡暴露給JS的方法

一個模組裡可以有很多方法,一些是可以暴露給JS直接呼叫的,一些是私有的不想暴露給JS,怎樣做到提取這些暴露的方法呢?我能想到的方法是對要暴露的方法名制定一些規則,比如用RCTExport_作為字首,然後用runtime方法class_getInstanceMethod取出所有方法名字,提取以RCTExport_為字首的方法,但這樣做噁心的地方是每個方法必須加字首。React Native用了另一種黑魔法似的方法解決這個問題:編譯屬性__attribute__。

在上述例子中我們看到模組方法裡有句程式碼:RCT_EXPORT(),模組裡的方法加上這個巨集就可以實現暴露給JS,無需其他規則,那這個巨集做了什麼呢?來看看它的定義:

這個巨集的作用是用編譯屬性__attribute__給二進位制檔案新建一個section,屬於__DATA資料段,名字為RCTExport,並在這個段里加入當前方法名。編譯器在編譯時會找到__attribute__進行處理,為生成的可執行檔案加入相應的內容。效果可以從linkmap看出來:

可以看到可執行檔案資料段多了個RCTExport段,內容就是各個要暴露給JS的方法。這些內容是可以在執行時獲取到的,在RCTBridge.m的RCTExportedMethodsByModuleID()方法裡獲取這些內容,提取每個方法的類名和方法名,就完成了提取模組裡暴露給JS方法的工作。

整體的模組類/方法提取實現在RCTRemoteModulesConfig()方法裡。

呼叫流程

接下來看看JS呼叫OC模組方法的詳細流程,包括callback回撥。這時需要細化一下上述的呼叫流程圖:

看起來有點複雜,不過一步步說明,應該很容易弄清楚整個流程,圖中每個流程都標了序號,從發起呼叫到執行回撥總共有11個步驟,詳細說明下這些步驟:

1.JS端呼叫某個OC模組暴露出來的方法。

2.把上一步的呼叫分解為ModuleName,MethodName,arguments,再扔給MessageQueue處理。

在初始化時模組配置表上的每一個模組都生成了對應的remoteModule物件,物件裡也生成了跟模組配置表裡一一對應的方法,這些方法裡可以拿到自身的模組名,方法名,並對callback進行一些處理,再移交給MessageQueue。具體實現在BatchedBridgeFactory.js的_createBridgedModule裡,整個實現區區24行程式碼,感受下JS的魔力吧。

3.在這一步把JS的callback函式快取在MessageQueue的一個成員變數裡,用CallbackID代表callback。在通過儲存在MessageQueue的模組配置表把上一步傳進來的ModuleName和MethodName轉為ModuleID和MethodID。

4.把上述步驟得到的ModuleID,MethodId,CallbackID和其他引數argus傳給OC。至於具體是怎麼傳的,後面再說。

5.OC接收到訊息,通過模組配置表拿到對應的模組和方法。

實際上模組配置表已經經過處理了,跟JS一樣,在初始化時OC也對模組配置表上的每一個模組生成了對應的例項並快取起來,模組上的每一個方法也都生成了對應的RCTModuleMethod物件,這裡通過ModuleID和MethodID取到對應的Module例項和RCTModuleMethod例項進行呼叫。具體實現在_handleRequestNumber:moduleID:methodID:params:。

6.RCTModuleMethod對JS傳過來的每一個引數進行處理。

RCTModuleMethod可以拿到OC要呼叫的目標方法的每個引數型別,處理JS型別到目標型別的轉換,所有JS傳過來的數字都是NSNumber,這裡會轉成對應的int/long/double等型別,更重要的是會為block型別引數的生成一個block。

例如-(void)select:(int)index response:(RCTResponseSenderBlock)callback 這個方法,拿到兩個引數的型別為int,block,JS傳過來的兩個引數型別是NSNumber,NSString(CallbackID),這時會把NSNumber轉為int,NSString(CallbackID)轉為一個block,block的內容是把回撥的值和CallbackID傳回給JS。

這些引數組裝完畢後,通過NSInvocation動態呼叫相應的OC模組方法。

7.OC模組方法呼叫完,執行block回撥。

8.呼叫到第6步說明的RCTModuleMethod生成的block。

9.block裡帶著CallbackID和block傳過來的引數去調JS裡MessageQueue的方法invokeCallbackAndReturnFlushedQueue。

10.MessageQueue通過CallbackID找到相應的JS callback方法。

11.呼叫callback方法,並把OC帶過來的引數一起傳過去,完成回撥。

整個流程就是這樣,簡單概括下,差不多就是:JS函式呼叫轉ModuleID/MethodID -> callback轉CallbackID -> OC根據ID拿到方法 -> 處理引數 -> 呼叫OC方法 -> 回撥CallbackID -> JS通過CallbackID拿到callback執行

事件響應

上述第4步留下一個問題,JS是怎樣把資料傳給OC,讓OC去調相應方法的?

答案是通過返回值。JS不會主動傳遞資料給OC,在調OC方法時,會在上述第4步把ModuleID,MethodID等資料加到一個佇列裡,等OC過來調JS的任意方法時,再把這個佇列返回給OC,此時OC再執行這個佇列裡要呼叫的方法。

一開始不明白,設計成JS無法直接呼叫OC,需要在OC去調JS時才通過返回值觸發呼叫,整個程式還能跑得通嗎。後來想想純native開發裡的事件響應機制,就有點理解了。native開發裡,什麼時候會執行程式碼?只在有事件觸發的時候,這個事件可以是啟動事件,觸控事件,timer事件,系統事件,回撥事件。而在React Native裡,這些事件發生時OC都會呼叫JS相應的模組方法去處理,處理完這些事件後再執行JS想讓OC執行的方法,而沒有事件發生的時候,是不會執行任何程式碼的,這跟native開發裡事件響應機制是一致的。

說到OC呼叫JS,再補充一下,實際上模組配置表除了有上述OC的模組remoteModules外,還儲存了JS模組localModules,OC調JS某些模組的方法時,也是通過傳遞ModuleID和MethodID去呼叫的,都會走到-enqueueJSCall:args:方法把兩個ID和引數傳給JS的BatchedBridge.callFunctionReturnFlushedQueue,跟JS調OC原理差不多,就不再贅述了。

總結

整個React Native的JS-OC通訊機制大致就是這樣了,關鍵點在於:模組化,模組配置表,傳遞ID,封裝呼叫,事件響應,其設計思想和實現方法很值得學習借鑑。

相關文章