React Native通信機制詳解

http://blog.cnbang.net/tech/2698/

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并通過回調獲取執行結果。:

1
2
3
4
5
6
7
8
9
//OC:
@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";
});

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

模塊配置表

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
{
????"remoteModuleConfig": {
????????"RCTSQLManager": {
????????????"methods": {
????????????????"query": {
????????????????????"type": "remote",
????????????????????"methodID": 0
????????????????}
????????????},
????????????"moduleID": 4
????????},
????????...
?????},
}

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

ReactNative1

在了解這個調用流程之前,我們先來看看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,無需其他規則,那這個宏做了什么呢?來看看它的定義:

1
2
#define RCT_EXPORT(JS_name) __attribute__((used, section("__DATA,RCTExport" \
))) static const char *__rct_export_entry__[] = { __func__, #JS_name }

這個宏的作用是用編譯屬性__attribute__給二進制文件新建一個section,屬于__DATA數據段,名字為RCTExport,并在這個段里加入當前方法名。編譯器在編譯時會找到__attribute__進行處理,為生成的可執行文件加入相應的內容。效果可以從linkmap看出來:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
# 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__
...

可以看到可執行文件數據段多了個RCTExport段,內容就是各個要暴露給JS的方法。這些內容是可以在運行時獲取到的,在RCTBridge.m的RCTExportedMethodsByModuleID()方法里獲取這些內容,提取每個方法的類名和方法名,就完成了提取模塊里暴露給JS方法的工作。

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

調用流程

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

ReactNative2

看起來有點復雜,不過一步步說明,應該很容易弄清楚整個流程,圖中每個流程都標了序號,從發起調用到執行回調總共有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,封裝調用,事件響應,其設計思想和實現方法很值得學習借鑒。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/259201.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/259201.shtml
英文地址,請注明出處:http://en.pswp.cn/news/259201.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++11系列學習之四----auto

在哪些情況下要申明類型: 定義變量類型 函數返回值,函數參數 表達式返回變量類型 auto關鍵字原理 在定義變量的時候必須申明類型,c是強語言類型,在編譯階段需要知道類型,這樣的好處是程序效率更高,而…

windows 自動copy遠程服務器文件

net use h: \\123.45.67.000\T1dbbackup 123456/user:administrator ------遠程服務器IP123.45.67.000 。T1dbbackup:共享文件夾 。 h :映射到本機的盤符。 用戶名:administrator,密碼:123456copy h:\*.* f:\T1DB ------復…

eclipse 不能切換輸入法

按了AltShift鍵?再按一次把EN切換成CN,然后再CtrlShift就可以切換輸入法轉載于:https://www.cnblogs.com/jiayonghua/p/3413827.html

excel打開2個獨立窗口_謝楠稱女性獨立的不是錢是心 謝楠與吳京婚后生育2個兒子...

近日,在綜藝節目《幸福三重奏》 三日談妻子篇中,謝楠被問到如何看待獨立女性時,反問記者會不會問吳京同樣的問題;隨后回答道,女性獨立的不是錢,而是你的心;楠姐的回答超級霸氣了,你們…

C++11系列學習之五-------decltype

使用場景 在C中經常要用到很長的變量名,如果已經有變量和你將使用的變量是一個類型,即可使用decltype關鍵字 來申明一樣的類型變量。 decltype原理 返回現有變量類型,decltype是一個關鍵字,而不是一個函數,這有啥區別…

Linux學習 Unit 9

Unit9.openssh-server1.openssh-server功能:讓遠程主機可以通過網絡訪問sshd服務,開始一個安全shell2.客戶端連接方式ssh遠程主機用戶遠程主機ip[rootdesktop0 ~]# ssh root172.25.0.11The authenticity of host 172.25.0.11 (172.25.0.11) cant be esta…

2015年創業中遇到的技術問題:41-50

41.Bootstrap換行。col-md-10和col-md-2。這2個div按說應該在一行的,結果col-md-2換行了。看看樣式,發現有多余的“margin-left: 1px;"。42.Service實現類定義了一個“自動調度進行刷新”的方法。OverrideScheduled(cron "0 0/10 * * * ? &q…

KMP模板與講解

讀書筆記終于寫完了,寫一下我對KMP的理解。 KMP的思想就是盡量利用已經得到的信息,來降低時間復雜度,已經得到的信息存放在next數組里。算法確實很難理解,所以很難講解。。舉個例子來說吧。 設字符串是str[],next[5] …

android 非root app 捕捉系統廣播_APP的生死之道

這篇文章主要介紹APP在安卓系統中是怎么被殺死的,按照怎樣的一個策略去釋放進程;同時介紹一些延長應用存活時間的方案,雖然這個在現在安卓系統上越來越難實現了,但是也是可以稍微了解下,主要也是通過這些hack的方案更好…

C++11系列學習之六-----for

前言C11這次的更新帶來了令很多C程序員期待已久的for range循環,每次看到javascript, lua里的for range,心想要是C能有多好,心里別提多酸了。這次C11不負眾望,再也不用羨慕別家人的for range了。使用場景ex1&#xff1…

ArcGIS Engine 10開發環境的一些常見問題(轉載)

轉自:http://bbs.esrichina-bj.cn/ESRI/viewthread.php?tid107612&extra&page1 許多版友在剛剛使用ArcGIS 10做開發的時候,都會遇到這樣那樣的問題。在擔任實習版主的這一個多月里,看到了這么幾個與開發環境相關的問題,重…

@value 靜態變量_面試官:為什么靜態方法不能調用非靜態方法和變量?

這個可能很多人之前學習jvm的時候都會遇到,屬于一個小問題,寫這篇文章的原因是我在看java相關的面試題目中遇到的,因此順手總結一下:一、例子我們先看效果:我們在靜態方法main中調用非靜態變量或者是方法都會報錯。我們…

SpringMVC連接多數據源配置

在spring-config-datasource.xml中配置&#xff1a; <ds:ibatis-config><ds:sql-map-clientid"sqlMapClient2"datasource-ref"riskBasicDataSource2"config-location"classpath:sqlmap-config.xml"/> </ds:ibatis-config> <…

Memcached 工作原理

http://hzp.iteye.com/blog/1872664Memcached處理的原子是每一個&#xff08;key&#xff0c;value&#xff09;對&#xff08;以下簡稱kv對&#xff09;&#xff0c;key會通過一個hash算法轉化成hash-key&#xff0c;便于查找、對比以及做到盡可能的散列。同時&#xff0c;mem…

C++11系列學習之七---------初始化列表

一、前言C的學習中&#xff0c;我想每個人都被變量定義和申明折磨過&#xff0c;比如我在大學筆試過的幾家公司&#xff0c;都考察了const和變量&#xff0c;類型的不同排列組合&#xff0c;讓你區別有啥不同。反正在學習C過程中已經被折磨慣了&#xff0c;今天再來看看重溫下那…

c# streamReader轉XmlDocument讀取節點

http獲得web&#xff08;url&#xff09;請求&#xff0c;先是獲得數據流streamreader&#xff0c;之后將String數據流轉換為xmldocument&#xff0c;之后xmlnode讀取節點。 // get the responseWebResponse webResponse webRequest.GetResponse();if (webResponse null){ re…

ad中電容用什么封裝_用什么來降低噪聲?只要幾個電容器就可以,簡單有效!...

使用電容器降低噪聲噪聲分很多種&#xff0c;性質也是多種多樣的。所以&#xff0c;噪聲對策(即降低噪聲的方法)也多種多樣。在這里主要談開關電源相關的噪聲&#xff0c;因此&#xff0c;請理解為DC電壓中電壓電平較低、頻率較高的噪聲。另外&#xff0c;除電容外&#xff0c;…

C#委托的介紹(delegate、Action、Func、predicate)

委托是一個類&#xff0c;它定義了方法的類型&#xff0c;使得可以將方法當作另一個方法的參數來進行傳遞。事件是一種特殊的委托。 1.委托的聲明 (1). delegate delegate我們常用到的一種聲明 Delegate至少0個參數&#xff0c;至多32個參數&#xff0c;可以無返回值&#xff0…

版本1.8.1Go安裝以及語法高亮配置

注意點&#xff1a;普通用戶和root用戶高亮要設置兩遍①下載go安裝包 https://golang.org/doc/ 最新的版本&#xff1a;go1.8.1.linux-amd64.tar.gz ②進入主目錄&#xff1a;$:su ~賦給普通用戶root權限&#xff0c;以便執行tar命令&#xff1a;$:su root 把壓縮包解壓到/usr/…

求二叉樹中節點的最大距離

struct node{ Node Left; Node Right; int MaxLeft;//左子樹到該節點的最長距離 int MaxRight;//右子樹到該節點的最長距離 char chValue; }; void FindMaxLen(Node T) { int tmpMax 0; if (NULL T) { return; } if (NULL T->Left) { T->MaxLeft 0; } if (NULL T-&g…