国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

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

2019-11-09 14:06:46
字體:
供稿:網(wǎng)友
React Native是Facebook剛開源的框架,可以使用javaScript直接開發(fā)原生APP。

概覽

React Native用iOS自帶的JavascriptCore作為JS的解析引擎,使用JavaScriptCore提供的一些可以讓JS與OC互調(diào)的特性,來實現(xiàn)JS和OC之間的交互。React Native通過各種手段,實現(xiàn)了在OC定義一個模塊方法,JS可以直接調(diào)用這個模塊方法并還可以無縫銜接回調(diào)。

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

123456789//OC:@implement RCTSQLManager- (void)query:(NSString *)queryData successCallback:(RCTResponseSenderBlOCk)responseSender{     RCT_EXPORT();     NSString *ret = @"ret"     responseSender(ret);}@end

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

模塊配置表

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

1234567891011121314{    "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):

1.jpg

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

1.取所有模塊類

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

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

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

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

12#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看出來:

1234567891011121314# Sections:# Address Size Segment Section0x100001670 0x000C0180 __TEXT __text...0x10011EFA0 0x00000330 __DATA RCTExport0x10011F2D0 0x00000010 __DATA __common0x10011F2E0 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)容是可以在運行時獲取到的。在運行時提取每個方法的類名和方法名,然后用前綴過濾,就完成了提取模塊里暴露給JS方法的工作。

調(diào)用流程

接下來看看JS調(diào)用OC模塊方法的詳細(xì)流程,包括callback回調(diào)。

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,再補充一下,實際上模塊配置表除了有上述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í)借鑒。

轉(zhuǎn)自:http://www.cocoachina.com/ios/20150401/11458.html,部分內(nèi)容有修改。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 泰州市| 宜川县| 巴中市| 日喀则市| 柏乡县| 旬阳县| 嘉兴市| 香格里拉县| 集贤县| 德江县| 无极县| 饶河县| 巍山| 托里县| 宝丰县| 林州市| 晋城| 东宁县| 博野县| 眉山市| 沾益县| 杭州市| 德庆县| 翁牛特旗| 英德市| 乳源| 镇安县| 莫力| 无极县| 克拉玛依市| 通许县| 东台市| 武汉市| 永泰县| 张家港市| 玛多县| 嘉兴市| 阳东县| 安顺市| 安塞县| 邢台市|