React Native通信機(jī)制詳解

React Native是facebook剛開源的框架速挑,可以用javascript直接開發(fā)原生APP酱酬,先不說這個框架后續(xù)是否能得到大眾認(rèn)可齿兔,單從源碼來說组题,這個框架源碼里有非常多的設(shè)計思想和實現(xiàn)方式值得學(xué)習(xí)葫男,本篇先來看看它最基礎(chǔ)的JavaScript-ObjectC通信機(jī)制(以下簡稱JS/OC)。

概覽

React Native用iOS自帶的JavaScriptCore作為JS的解析引擎往踢,但并沒有用到JavaScriptCore提供的一些可以讓JS與OC互調(diào)的特性,而是自己實現(xiàn)了一套機(jī)制徘层,這套機(jī)制可以通用于所有JS引擎上峻呕,在沒有JavaScriptCore的情況下也可以用webview代替,實際上項目里就已經(jīng)有了用webview作為解析引擎的實現(xiàn)趣效,應(yīng)該是用于兼容iOS7以下沒有JavascriptCore的版本瘦癌。

普通的JS-OC通信實際上很簡單,OC向JS傳信息有現(xiàn)成的接口跷敬,像webview提供的-stringByEvaluatingJavaScriptFromString方法可以直接在當(dāng)前context上執(zhí)行一段JS腳本讯私,并且可以獲取執(zhí)行后的返回值,這個返回值就相當(dāng)于JS向OC傳遞信息西傀。React Native也是以此為基礎(chǔ)斤寇,通過各種手段,實現(xiàn)了在OC定義一個模塊方法拥褂,JS可以直接調(diào)用這個模塊方法并還可以無縫銜接回調(diào)娘锁。

舉個例子,OC定義了一個模塊RCTSQLManager饺鹃,里面有個方法-query:successCallback:莫秆,JS可以直接調(diào)用RCTSQLManager.query并通過回調(diào)獲取執(zhí)行結(jié)果。:

@implement RCTSQLManager

- (void)query:(NSString *)queryData successCallback:(RCTResponseSenderBlOCk)responseSender

{

RCT_EXPORT();

NSString *ret = @"ret"

responseSender(ret);

}

@end

1

2

3

4

//JS:

RCTSQLManager.query("SELECT * FROM table", function(result) {

//result == "ret";

});

接下來看看它是怎樣實現(xiàn)的悔详。

模塊配置表

首先OC要告訴JS它有什么模塊镊屎,模塊里有什么方法,JS才知道有這些方法后才有可能去調(diào)用這些方法茄螃。這里的實現(xiàn)是OC生成一份模塊配置表傳給JS缝驳,配置表里包括了所有模塊和模塊里方法的信息。例:

01

{

"remoteModuleConfig": {

"RCTSQLManager": {

"methods": {

"query": {

"type": "remote",

"methodID": 0

}

},

"moduleID": 4

},

...

},

}

OC端和JS端分別各有一個bridge归苍,兩個bridge都保存了同樣一份模塊配置表党巾,JS調(diào)用OC模塊方法時,通過bridge里的配置表把模塊方法轉(zhuǎn)為模塊ID和方法ID傳給OC霜医,OC通過bridge的模塊配置表找到對應(yīng)的方法執(zhí)行之齿拂,以上述代碼為例,流程大概是這樣(先不考慮callback):

ReactNative1

在了解這個調(diào)用流程之前肴敛,我們先來看看OC的模塊配置表式怎么來的署海。我們在新建一個OC模塊時吗购,JS和OC都不需要為新的模塊手動去某個地方添加一些配置,模塊配置表是自動生成的砸狞,只要項目里有一個模塊捻勉,就會把這個模塊加到配置表上,那這個模塊配置表是怎樣自動生成的呢刀森?分兩個步驟:

1.取所有模塊類

每個模塊類都實現(xiàn)了RCTBridgeModule接口踱启,可以通過runtime接口objc_getClassList或objc_copyClassList取出項目里所有類,然后逐個判斷是否實現(xiàn)了RCTBridgeModule接口研底,就可以找到所有模塊類埠偿,實現(xiàn)在RCTBridgeModuleClassesByModuleID()方法里。

2.取模塊里暴露給JS的方法

一個模塊里可以有很多方法榜晦,一些是可以暴露給JS直接調(diào)用的冠蒋,一些是私有的不想暴露給JS,怎樣做到提取這些暴露的方法呢乾胶?我能想到的方法是對要暴露的方法名制定一些規(guī)則抖剿,比如用RCTExport_作為前綴,然后用runtime方法class_getInstanceMethod取出所有方法名字识窿,提取以RCTExport_為前綴的方法斩郎,但這樣做惡心的地方是每個方法必須加前綴。React Native用了另一種黑魔法似的方法解決這個問題:編譯屬性__attribute__喻频。

在上述例子中我們看到模塊方法里有句代碼:RCT_EXPORT()孽拷,模塊里的方法加上這個宏就可以實現(xiàn)暴露給JS,無需其他規(guī)則半抱,那這個宏做了什么呢脓恕?來看看它的定義:

1

2

#define RCT_EXPORT(JS_name) __attribute__((used, section("__DATA,RCTExport" \

))) static const char *__rct_export_entry__[] = { __func__, #JS_name }

這個宏的作用是用編譯屬性__attribute__給二進(jìn)制文件新建一個section,屬于__DATA數(shù)據(jù)段窿侈,名字為RCTExport炼幔,并在這個段里加入當(dāng)前方法名。編譯器在編譯時會找到__attribute__進(jìn)行處理,為生成的可執(zhí)行文件加入相應(yīng)的內(nèi)容。效果可以從linkmap看出來:





# Sections:

# Address Size Segment Section

0x100001670 0x000C0180 __TEXT __text

...

0x10011EFA0 0x00000330 __DATA RCTExport

0x10011F2D0 0x00000010 __DATA __common

0x10011F2E0 0x000003B8 __DATA __bss

...

0x10011EFA0 0x00000010 [ 4] -[RCTStatusBarManager setStyle:animated:].__rct_export_entry__

0x10011EFB0 0x00000010 [ 4] -[RCTStatusBarManager setHidden:withAnimation:].__rct_export_entry__

0x10011EFC0 0x00000010 [ 5] -[RCTSourceCode getScriptText:failureCallback:].__rct_export_entry__

0x10011EFD0 0x00000010 [ 7] -[RCTAlertManager alertWithArgs:callback:].__rct_export_entry__

...

可以看到可執(zhí)行文件數(shù)據(jù)段多了個RCTExport段菱蔬,內(nèi)容就是各個要暴露給JS的方法。這些內(nèi)容是可以在運(yùn)行時獲取到的跺讯,在RCTBridge.m的RCTExportedMethodsByModuleID()方法里獲取這些內(nèi)容,提取每個方法的類名和方法名殉农,就完成了提取模塊里暴露給JS方法的工作刀脏。

整體的模塊類/方法提取實現(xiàn)在RCTRemoteModulesConfig()方法里。

調(diào)用流程

接下來看看JS調(diào)用OC模塊方法的詳細(xì)流程超凳,包括callback回調(diào)愈污。這時需要細(xì)化一下上述的調(diào)用流程圖:

ReactNative2

看起來有點復(fù)雜耀态,不過一步步說明,應(yīng)該很容易弄清楚整個流程暂雹,圖中每個流程都標(biāo)了序號首装,從發(fā)起調(diào)用到執(zhí)行回調(diào)總共有11個步驟,詳細(xì)說明下這些步驟:

1.JS端調(diào)用某個OC模塊暴露出來的方法杭跪。

2.把上一步的調(diào)用分解為ModuleName,MethodName,arguments仙逻,再扔給MessageQueue處理。

在初始化時模塊配置表上的每一個模塊都生成了對應(yīng)的remoteModule對象涧尿,對象里也生成了跟模塊配置表里一一對應(yīng)的方法系奉,這些方法里可以拿到自身的模塊名,方法名现斋,并對callback進(jìn)行一些處理喜最,再移交給MessageQueue偎蘸。具體實現(xiàn)在BatchedBridgeFactory.js的_createBridgedModule里庄蹋,整個實現(xiàn)區(qū)區(qū)24行代碼,感受下JS的魔力吧迷雪。

3.在這一步把JS的callback函數(shù)緩存在MessageQueue的一個成員變量里限书,用CallbackID代表callback。在通過保存在MessageQueue的模塊配置表把上一步傳進(jìn)來的ModuleName和MethodName轉(zhuǎn)為ModuleID和MethodID章咧。

4.把上述步驟得到的ModuleID,MethodId,CallbackID和其他參數(shù)argus傳給OC倦西。至于具體是怎么傳的,后面再說赁严。

5.OC接收到消息扰柠,通過模塊配置表拿到對應(yīng)的模塊和方法。

實際上模塊配置表已經(jīng)經(jīng)過處理了疼约,跟JS一樣卤档,在初始化時OC也對模塊配置表上的每一個模塊生成了對應(yīng)的實例并緩存起來,模塊上的每一個方法也都生成了對應(yīng)的RCTModuleMethod對象程剥,這里通過ModuleID和MethodID取到對應(yīng)的Module實例和RCTModuleMethod實例進(jìn)行調(diào)用劝枣。具體實現(xiàn)在_handleRequestNumber:moduleID:methodID:params:。

6.RCTModuleMethod對JS傳過來的每一個參數(shù)進(jìn)行處理织鲸。

RCTModuleMethod可以拿到OC要調(diào)用的目標(biāo)方法的每個參數(shù)類型舔腾,處理JS類型到目標(biāo)類型的轉(zhuǎn)換,所有JS傳過來的數(shù)字都是NSNumber搂擦,這里會轉(zhuǎn)成對應(yīng)的int/long/double等類型稳诚,更重要的是會為block類型參數(shù)的生成一個block。

例如-(void)select:(int)index response:(RCTResponseSenderBlock)callback 這個方法瀑踢,拿到兩個參數(shù)的類型為int,block采桃,JS傳過來的兩個參數(shù)類型是NSNumber,NSString(CallbackID)懒熙,這時會把NSNumber轉(zhuǎn)為int,NSString(CallbackID)轉(zhuǎn)為一個block普办,block的內(nèi)容是把回調(diào)的值和CallbackID傳回給JS工扎。

這些參數(shù)組裝完畢后,通過NSInvocation動態(tài)調(diào)用相應(yīng)的OC模塊方法衔蹲。

7.OC模塊方法調(diào)用完肢娘,執(zhí)行block回調(diào)。

8.調(diào)用到第6步說明的RCTModuleMethod生成的block舆驶。

9.block里帶著CallbackID和block傳過來的參數(shù)去調(diào)JS里MessageQueue的方法invokeCallbackAndReturnFlushedQueue橱健。

10.MessageQueue通過CallbackID找到相應(yīng)的JS callback方法。

11.調(diào)用callback方法沙廉,并把OC帶過來的參數(shù)一起傳過去拘荡,完成回調(diào)。

整個流程就是這樣撬陵,簡單概括下珊皿,差不多就是:JS函數(shù)調(diào)用轉(zhuǎn)ModuleID/MethodID -> callback轉(zhuǎn)CallbackID -> OC根據(jù)ID拿到方法 -> 處理參數(shù) -> 調(diào)用OC方法 -> 回調(diào)CallbackID -> JS通過CallbackID拿到callback執(zhí)行

事件響應(yīng)

上述第4步留下一個問題,JS是怎樣把數(shù)據(jù)傳給OC巨税,讓OC去調(diào)相應(yīng)方法的蟋定?

答案是通過返回值。JS不會主動傳遞數(shù)據(jù)給OC草添,在調(diào)OC方法時驶兜,會在上述第4步把ModuleID,MethodID等數(shù)據(jù)加到一個隊列里,等OC過來調(diào)JS的任意方法時远寸,再把這個隊列返回給OC抄淑,此時OC再執(zhí)行這個隊列里要調(diào)用的方法。

一開始不明白驰后,設(shè)計成JS無法直接調(diào)用OC肆资,需要在OC去調(diào)JS時才通過返回值觸發(fā)調(diào)用,整個程序還能跑得通嗎倡怎。后來想想純native開發(fā)里的事件響應(yīng)機(jī)制迅耘,就有點理解了。native開發(fā)里监署,什么時候會執(zhí)行代碼颤专?只在有事件觸發(fā)的時候,這個事件可以是啟動事件钠乏,觸摸事件栖秕,timer事件,系統(tǒng)事件晓避,回調(diào)事件簇捍。而在React Native里只壳,這些事件發(fā)生時OC都會調(diào)用JS相應(yīng)的模塊方法去處理,處理完這些事件后再執(zhí)行JS想讓OC執(zhí)行的方法暑塑,而沒有事件發(fā)生的時候吼句,是不會執(zhí)行任何代碼的,這跟native開發(fā)里事件響應(yīng)機(jī)制是一致的事格。

說到OC調(diào)用JS惕艳,再補(bǔ)充一下,實際上模塊配置表除了有上述OC的模塊remoteModules外驹愚,還保存了JS模塊localModules远搪,OC調(diào)JS某些模塊的方法時,也是通過傳遞ModuleID和MethodID去調(diào)用的逢捺,都會走到-enqueueJSCall:args:方法把兩個ID和參數(shù)傳給JS的BatchedBridge.callFunctionReturnFlushedQueue谁鳍,跟JS調(diào)OC原理差不多,就不再贅述了劫瞳。

總結(jié)

整個React Native的JS-OC通信機(jī)制大致就是這樣了倘潜,關(guān)鍵點在于:模塊化,模塊配置表柠新,傳遞ID窍荧,封裝調(diào)用辉巡,事件響應(yīng)恨憎,其設(shè)計思想和實現(xiàn)方法很值得學(xué)習(xí)借鑒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末郊楣,一起剝皮案震驚了整個濱河市憔恳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌净蚤,老刑警劉巖钥组,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異今瀑,居然都是意外死亡程梦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門橘荠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屿附,“玉大人,你說我怎么就攤上這事哥童⊥Ψ荩” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵贮懈,是天一觀的道長匀泊。 經(jīng)常有香客問我优训,道長,這世上最難降的妖魔是什么各聘? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任揣非,我火速辦了婚禮,結(jié)果婚禮上躲因,老公的妹妹穿的比我還像新娘妆兑。我一直安慰自己,他們只是感情好毛仪,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布搁嗓。 她就那樣靜靜地躺著,像睡著了一般箱靴。 火紅的嫁衣襯著肌膚如雪腺逛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天衡怀,我揣著相機(jī)與錄音棍矛,去河邊找鬼。 笑死抛杨,一個胖子當(dāng)著我的面吹牛够委,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播怖现,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茁帽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了屈嗤?” 一聲冷哼從身側(cè)響起潘拨,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饶号,沒想到半個月后铁追,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茫船,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年琅束,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片算谈。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡涩禀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出濒生,到底是詐尸還是另有隱情埋泵,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站丽声,受9級特大地震影響礁蔗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜雁社,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一浴井、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧霉撵,春花似錦磺浙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至喇完,卻和暖如春伦泥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锦溪。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工不脯, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人刻诊。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓防楷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親则涯。 傳聞我的和親對象是個殘疾皇子复局,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容