ReactNative iOS源碼解析

摘自?

折騰范兒の味精 博文。

ReactNative 概要

ReactNative,動態,跨平臺,熱更新,這幾個詞現在越來越火了,一句使用JavaScript寫原生App吸引力了無數人的眼球,并且誕生了這么久也逐漸趨于穩定,攜程,天貓,QZone也都在大產品線的業務上,部分模塊采用這個方案上線,并且效果得到了驗證(見2016 GMTC 資料PPT)

我們把這個單詞拆解成2部分

React

熟悉前端的朋友們可能都知道React.JS這個前端框架,沒錯整個RN框架的JS代碼部分,就是React.JS,所有這個框架的特點,完完全全都可以在RN里面使用(這里還融入了Flux,很好的把傳統的MVC重組為dispatch,store和components,Flux架構

所以說,寫RN哪不懂了,去翻React.JS的文檔或許都能給你解答。

Native

顧名思義,純原生的native體驗,純原生的UI組件,純原生的觸摸響應,純原生的模塊功能

那么這兩個不相干的東西是如何關聯在一起的呢?

React.JS是一個前端框架,在瀏覽器內H5開發上被廣泛使用,他在渲染render()這個環節,在經過各種flexbox布局算法之后,要在確定的位置去繪制這個界面元素的時候,需要通過瀏覽器去實現。他在響應觸摸touchEvent()這個環節,依然是需要瀏覽器去捕獲用戶的觸摸行為,然后回調React.JS

上面提到的都是純網頁,純H5,但如果我們把render()這個事情攔截下來,不走瀏覽器,而是走native會怎樣呢?

當React.JS已經計算完每個頁面元素的位置大小,本來要傳給瀏覽器,讓瀏覽器進行渲染,這時候我們不傳給瀏覽器了,而是通過一個JS/OC的橋梁,去通過[[UIView alloc]initWithFrame:frame]的OC代碼,把這個界面元素渲染了,那我們就相當于用React.JS繪制出了一個native的View

拿我們剛剛繪制出得native的View,當他發生native源生的- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event觸摸事件的時候,通過一個OC/JS的橋梁,去調用React.JS里面寫好的點擊事件JS代碼

這樣React.JS還是那個React.JS,他的使用方法沒發生變化,但是卻獲得了純源生native的體驗,native的組件渲染,native的觸摸響應

于是,這個東西就叫做React-Native。

ReactNative 結構

大家可以看到,剛才我說的核心就是一個橋梁,無論是JS=>OC,還是OC=>JS。

剛才舉得例子,就相當于把純原生的UI模塊,接入這個橋梁,從而讓原生UI與React.JS融為一體。

那我們把野心放長遠點,我們不止想讓React.JS操作UI,我還想用JS操作數據庫!無論是新玩意Realm,還是老玩意CoreData,FMDB,我都希望能用JS操作應該怎么辦?好辦,把純原生的DB代碼模塊,接入這個橋梁

如果我想讓JS操作Socket做長連接呢?好辦,把原生socket代碼模塊接入這個橋梁。如果我想讓JS能操作支付寶,微信,蘋果IAP呢?好辦,把原生支付代碼模塊接入這個橋梁

由此可見RN就是由一個bridge橋梁,連接起了JS與na的代碼模塊

鏈接了哪個模塊,哪個模塊就能用JS來操作,就能動態更新

發現現有RN框架有些功能做不到了?擴展寫個native代碼模塊,接入這個橋梁

這是一個極度模塊化可擴展的橋梁框架,不是說你從facebook的源上拉下來RN的代碼,RN的能力就固定一成不變了,他的模塊化可擴展,讓你缺啥補上啥就好了。

ReactNative 結構圖

大家可以看這個結構圖,整個RN的結構分為四個部分,上面提到的,RN橋的模塊化可擴展性,就體現在JSBridge/OCBridge里的ModuleConfig,只要遵循RN的協議RCTBridgeModule去寫的OC Module對象,使用RCT_EXPORT_MODULE()宏注冊類,使用RCT_EXPORT_METHOD()宏注冊方法,那么這個OC Module以及他的OC Method都會被JS與OC的ModuleConfig進行統一控制。

上面是RN的代碼類結構圖

○ 大家可以看到RCTRootView是RN的根視圖。

? ? ? ○ 他內部持有了一個RCTBridge,但是這個RCTBridge并沒有太多的代碼,而是持有了另一個 ? ? ? ? ??RCTBatchBridge對象,大部分的業務邏輯都轉發給BatchBridge,BatchBridge里面寫 ? ? ? ? ? ? ? 著的大量的核心代碼

? ? ? ? ? ? ? ○ BatchBridge會通過RCTJavaScriptLoader來加載JSBundle,在加載完畢后,這個 ? ? ? ? ? ? ? ? ? ?loader也沒什么太大的用了

? ? ? ? ? ? ? ○ BatchBridge會持有一個RCTDisplayLink,這個對象主要用于一些Timer, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Navigator的Module需要按著屏幕渲染頻率回調JS用的,只是給部分Module需求使 ? ? ? ? ? ? ? ? ? ?用

? ? ? ? ? ? ? ○ RCTModuleXX所有的RN的Module組件都是RCTModuleData,無論是RN的核心系 ? ? ? ? ? ? ? ? ?統組件,還是擴展的UI組件,API組件

? ? ? ? ? ? ? ? ? ? ? ? ○ RCTJSExecutor是一個很特殊的RCTModuleData,雖然他被當做組件 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?module一起管理,統一注冊,但他是系統組件的核心之一,他負責單獨開一 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?個線程,執行JS代碼,處理JS回調,是bridge的核心通道

? ? ? ? ? ? ? ? ? ? ? ? ○ RCTEventDispatcher也是一個很特殊的RCTModuleData,雖然他被當做組 ? ? ? ? ? ? ? ? ? ? ? ? ? ?件module一起管理,統一注冊,但是他負責的是各個業務模塊通過他主動發 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 起調用js,比如UIModule,發生了點擊事件,是通過他主動回調JS的,他回調 ? ? ? ? ? ? ? ? ? ? ? ? ? ? JS也是通過RCTJSExecutor來操作,他的作用是封裝了eventDispatcher得API ? ? ? ? ? ? ? ? ? ? ? ? ? ?來方便業務Module使用。



ReactNative 初始化代碼分析

我會按著函數調用棧類似的形式梳理出一個代碼流程表,對每一個調用環節進行簡單標記與作用說明,在整個表梳理完畢后,我會一一把每個標記進行詳細的源碼分析和解釋

下面的代碼流程表,如果有類名+方法的,你可以直接在RN源碼中定位到具體代碼段

○RCTRootView-initWithBundleURLXXX(RootInit標記)

? ? ○RCTBridge-initWithBundleXXX

? ? ? ? ○RCTBridge-createBatchedBridge(BatchBridgeInit標記

? ? ? ? ? ? ○New Displaylink(DisplaylinkInit標記

? ? ? ? ? ? ○New dispatchQueue (dispatchQueueInit標記)

? ? ? ? ? ? ○New dispatchGroup (dispatchGroupInit標記)

? ? ? ? ? ? ○group Enter(groupEnterLoadSource標記

? ? ? ? ? ? ? ? ? ○RCTBatchedBridge-loadSource (loadJS標記)

? ? ? ? ? ? ○RCTBatchedBridge-initModulesWithDispatchGroup(InitModule標記這塊內容非 ? ? ? ? ? ? ? ? 常多,有個子代碼流程表)

? ? ? ? ? ? ○group Enter(groupEnterJSConfig標記

? ? ? ? ? ? ? ? ? ○RCTBatchedBridge-setUpExecutor(configJSExecutor標記

? ? ? ? ? ? ? ? ? ○RCTBatchedBridge-moduleConfig(moduleConfig標記

? ? ? ? ? ? ? ? ? ○RCTBatchedBridge-injectJSONConfiguration(moduleConfigInject標記

? ? ? ? ? ? ○group Notify(groupDone標記

? ? ? ? ? ? ? ? ? ○RCTBatchedBridge-executeSourceCode(evaluateJS標記

? ? ? ? ? ? ? ? ? ○RCTDisplayLink-addToRunLoop(addrunloop標記



RootInit標記:所有RN都是通過init方法創建的不再贅述,URL可以是網絡url,也可以是本地filepath轉成URL

BatchBridgeInit標記:前邊說過rootview會先持有一個RCTBridge,所有的module都是直接操作bridge所提供的接口,但是這個bridge基本上不干什么核心邏輯代碼,他內部持有了一個batchbridge,各種調用都是直接轉發給RCTBatchBridge來操作,因此batchbridge才是核心

RCTBridge在init的時候調用[self setUp]

RCTBridge在setUp的時候調用[self createBatchedBridge]

DisplaylinkInit標記:batchbridge會首先初始化一個RCTDisplayLink這個東西在業務邏輯上不會被所有的module調用,他的作用是以設備屏幕渲染的頻率觸發一個timer,判斷是否有個別module需要按著timer去回調js,如果沒有module,這個模塊其實就是空跑一個displaylink,注意,此時只是初始化,并沒有run這個displaylink

dispatchQueueInit標記:會初始化一個GCDqueue,后面很多操作都會被扔到這個隊列里,以保證順序執行

dispatchGroupInit標記:后面接下來進行的一些列操作,都會被添加到這個GCDgroup之中,那些被我做了group Enter標記的,當group內所有事情做完之后,會觸發group Notify

groupEnterLoadSource標記:會把無論是從網絡還是從本地,拉取jsbundle這個操作,放進GCDgroup之中,這樣只有這個操作進行完了(還有其他group內操作執行完了,才會執行notify的任務)

loadJS標記:其實就是異步去拉取jsbundle,無論是本地讀還是網絡啦,[RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onComplete:onSourceLoad];只有當回調完成之后會執行dispatch_group_leave,離開group

InitModule標記:這個函數是在主線程被執行的,但是剛才生成的GCD group會被當做參數傳進內部,因為內部的一些邏輯是需要加入group的,這個函數內部很復雜 我會繼續繪制一個代碼流程表

1)RCTGetModuleClasses()

一個C函數,RCT_EXPORT_MODULE()注冊宏會在+load時候把Module類都統一管理在一個static NSArray里,通過RCTGetModuleClasses()可以取出來所有的Module

2)RCTModuleData-initWithModuleClass

此處是一個for循環,循環剛才拿到的array,對每一個注冊了得module都循環生成RCTModuleData實例

3)配置moduleConfig

每一個module在循環生成結束后,bridge會統一存儲3分配置表,包含了所有的moduleConfig的信息,便于查找和管理.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容