ReactNative iOS源碼解析(二)

遷移老文章到掘金(基於RN 0.26)javascript

相關係列文章html

上一篇瞭解了 ReactNative是如何初始化一整套JS/OC通訊機制,是如何相互通訊的。通篇在講JS/OC的通訊的源代碼流程,解釋了爲何JS能夠調用OC,爲何OC能夠調用JS,這相互之間的通訊,是如何經過代碼進行控制與管理的java

可是上一篇講的內容有一點太抽象了,全都是底層通訊,咱們依然不知道:node

上層的業務module是如何一步步用js搭建出一款app的?react

因而就進入了今天的環節,ReactNative中的Native,具體講講各類各樣的Module是如何工做的,官方寫好的Module以及咱們能夠自行擴展的Moduleios

這裏面分爲2種modulegit

  • 源生API模塊 - RCTModuleData

(說明,官方文檔把這個起名就叫源生模塊,英文Module,我這裏先中二的起名叫APIModule,爲了和另外一個區別起名一下,瞎起的名字,你們湊合一下)github

  • 源生UI組件模塊 - RCTComponentData

(說明,官方文檔把這個起名就叫源生UI組件,英文Component,我這裏先中二的起名叫UIModule,爲了和另外一個區別起名,瞎起的名字,你們湊合一下)json

API模塊闡述了JS是如何調用native各個模塊的邏輯react-native

UI組件闡述了JS是如何建立出native的UI界面

本文在源碼分析部分,對照前文的代碼流程能夠加深理解

源生API型模塊

什麼叫APIModule?

APIModule是一種面向過程式的模塊調用

JS只須要用一個模塊名,一個API名,就能經過bridge找到對應的native的方法進行調用,JS Call OC Method

這個過程就是一個函數調用而已,不存在操做某個實例對象,只是傳遞參數,操做參數,處理邏輯,返回數值 OC Call JS(經過前文知道,bridge都是異步的,經過callback block返回)

舉個例子好了,對於系統alert彈框,分享微信朋友圈,這種功能是最適合使用APIModule的

如何使用APIModule呢?

其實ReactNative原生模塊 中文文檔上面詳細介紹瞭如何使用APIModule,由於一會咱們還要詳細看源碼,我這裏還會再簡單複數一遍。

假如咱們想讓RN擁有,iOS系統彈框這一個功能:

第一步,先寫一個APIModule對象,聽從RCTBridgeModule協議

#import "RCTBridgeModule.h"

@interface VKAlertModule : NSObject<RCTBridgeModule>

@end
複製代碼

第二步,在實現文件裏寫一個宏RCT_EXPORT_MODULE()

第三步,實現這個Module能爲RN提供的API。

若是我打算寫這樣一個函數

-(void)nativeAlert:(NSString *)content withButton:(NSString *)name

讓RN去調用,那麼按着文檔我須要去掉-(void)後的全部內容,寫進一個宏裏面RCT_EXPORT_METHOD(xxx)

整個代碼就會是這樣

@implementation VKAlertModule

RCT_EXPORT_MODULE()

RCT_EXPORT_METHOD(nativeAlert:(NSString *)content withButton:(NSString *)name){
    
   // Use UIAlertView create a alert
}
@end
複製代碼

一個最簡單的APIModule就寫好了,在JS裏面想要使用這個APIModule,只須要這樣寫就OK了

import { NativeModules } from 'react-native';
var VKAlertModule = VKAlertModule;

//而後在須要調用的地方
VKAlertModule.nativeAlert('這是一個系統彈框','肯定')
複製代碼

能夠看到,在JS中模塊名就是咱們建立的類名,function名就是咱們寫的OC函數中,第一個參數之前的那一部分(只保留第一個參數前的nativeAlert爲名字,後面的withButton什麼的都不算了)

咱們以前在源碼中提到,JS是能夠把回調傳回來的,那咱們就改兩筆,加入回調的形式

//OC側代碼
@implementation VKAlertModule

RCT_EXPORT_MODULE()

RCT_EXPORT_METHOD(nativeAlert:(NSString *)content withButton:(NSString *)name callback:(RCTResponseSenderBlock)callback){
   // Use UIAlertView create a alert
   // show alert
   // 持有 block
   // when alert button click ok
   // use block callback
}
@end

//JS側代碼
import { NativeModules } from 'react-native';
var VKAlertModule = VKAlertModule;

//而後在須要調用的地方
VKAlertModule.nativeAlert('這是一個系統彈框','肯定',function(){
	console.log('clickok');
})

複製代碼

此時咱們雖然不知道是怎麼回事,只是照着文檔作了,但看起來,JS已經徹底能任意的調用APIModule提供的native能力了

使用RN寫一個系統alert要作這麼多工做麼?固然不是,facebook已經幫你寫好了一個很是大而全的Alert的APIModule,RCTAlertManager,因此你徹底能夠按着上面的思路去打開RN源碼裏面的RCTAlertManager類,去看看和學習如何寫一個功能強大的APIModule

系統alert這種通用型的需求,facebook幫你寫好了,可是若是是分享微信微博朋友圈之類的,facebook固然就不可能把這麼獨特的中國化的需求提早給你作好,可是不用慌,相信你也能本身寫出來同樣強大的shareManager-APIModule

APIModule的源碼是如何運做的呢?

知其然知其因此然

咱們只清楚了,如何按着文檔寫一個APIModule,如何直接讓JS去使用module,但爲何會這樣,這裏面代碼是怎麼運做的,徹底是一頭霧水,那麼就深刻源碼,看看這幾個宏是怎麼樣能產生這樣神奇的功效的

RCT_EXPORT_MODULE() 註冊APIModule宏

這是一個宏套函數的過程,徹底展開一下能夠看到

//宏展開
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
//宏裏面調用的函數
void RCTRegisterModule(Class moduleClass)
{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    RCTModuleClasses = [NSMutableArray new];
  });

  RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
            @"%@ does not conform to the RCTBridgeModule protocol",
            moduleClass);

  // Register module
  [RCTModuleClasses addObject:moduleClass];
}
複製代碼

能夠看到寫了這個宏就自動幫你寫好了2個method實現

一個是自動寫好了+moduleName的實現,返回了@#js_name@#的意思是自動把宏的參數js_name轉成字符,但咱們剛纔的樣例裏,都是直接不寫參數的註冊宏,因此說若是註冊的時候不寫參數,+moduleName會返回空,此處先不細說,後面會提到

另外一個是自動寫了+load的實現,+load你們都知道,app一運行就會執行一次,全部的類都會執行一次,因此在app運行的時候,你寫的這個module類就會自動的執行了RCTRegisterModule這個函數,這個函數幹了些什麼事情呢?首先在內存中建立了一個單例RCTModuleClasses表(上一篇中提到過),而後判斷你寫的類是否聽從RCTBridgeModule協議(這也是爲何要求你在寫module定義的時候必定要組從協議),而後把你寫的moduleClass放入內從的單例RCTModuleClasses表中

RCT_EXPORT_METHOD() 導出方法宏

這又是一個宏套宏,看着會有一點晦澀

//最外層宏
#define RCT_EXPORT_METHOD(method) \
  RCT_REMAP_METHOD(, method)
  
//內一層
#define RCT_REMAP_METHOD(js_name, method) \
  RCT_EXTERN_REMAP_METHOD(js_name, method) \
  - (void)method

//內二層
#define RCT_EXTERN_REMAP_METHOD(js_name, method) \
  + (NSArray<NSString *> *)RCT_CONCAT(__rct_export__, \
    RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) { \
    return @[@#js_name, @#method]; \
  }
複製代碼

能夠看一下咱們把-(void)nativeAlert:(NSString *)content withButton:(NSString *)name這麼長一串剪裁掉-(void)都扔進最外層宏當作參數了,最外層基本上沒處理什麼,直接調用內一層宏,第一個參數傳空,第二個參數透傳

看一下內一層宏幹了啥,內一層宏除了2個參數透傳給內二層宏以外,還從新補全了-(void),恢復了一個完整OC語法的函數定義,這樣才使得RCT_EXPORT_METHOD(xxx)這樣寫一個函數編譯器不會報錯

最重要的內二層咱們看看都作了啥,RCT_CONCAT又是一個宏,這個宏我就不展開了,他基本上就是實現了一個宏的拼接,最早把__LINE____COUNTER__進行拼接,這是兩個C語言宏,分別表明着行號與一個內置計數器,我沒有詳細去跟這兩個數字的具體表現,大概意思就是爲每個RCT_EXPORT_METHOD生成一個惟一識別的數字tag在將這個tag與js_name拼接(此處其實js_name爲空字符串),而後在前面拼接上一個__rct_export__,用宏生成了一個返回NSArray的方法

說這有點繞舉個例子就行了,假設咱們寫RCT_EXPORT_METHOD(nativeAlert:xxx)的時候,__LINE____COUNTER__組合起來的數字tag若是是123456,那麼這個內二層宏還會自動生成一個這樣的函數

+ (NSArray<NSString *> *)__rct_export__123456{ 
    return @[@"", @"nativeAlert:xxx"]; 
  }
複製代碼

換句話說,一行RCT_EXPORT_METHOD(xxxx),等於生成了2個函數的實現。

  • -(void)nativeAlert:(NSString *)content withButton:(NSString *)name
  • +(NSArray<NSString *> *)__rct_export__123456

咱們native註冊的這些modules表,導出的這些自動生成的方法,JS是怎麼知道的?怎麼調用的?

這就緊密聯繫前一篇文章提到的RCTRootView的初始化環節中的幾個重要標記了

  • InitModule標記
  • moduleConfig標記
  • moduleConfigInject標記
  • evaluateJS標記

InitModule的時候,就會從單例RCTModuleClasses表中拿出全部的APIModule的class對象,循環去建立RCTModuleData實例(上文提到過RCTModuleData不是APIModule,而是包裝了一下APIModule,RCTModuleData.instance纔是APIModule),而且一一保存在RCTBatchBridge對象的三個表中

  • moduleClassesByID數組表,枚舉APIModule的class,添加進入數組
  • moduleDataByID數組表,枚舉由APIModule生成的RCTModuleData對象,添加進入數組
  • moduleDataByName字典表,以+methodName方法的返回值爲key,枚舉由APIModule生成的RCTModuleData對象,添加進入字典 (剛纔不是說註冊宏咱們歷來都不填參數,致使+methodName返回爲空字符串麼,這裏經過RCTBridgeModuleNameForClass方法,若是是空字符串會自動返回類名字符串)

moduleConfig的時候,RCTBatchBridge會循環moduleDataByID數組表,把每個APIModule的name都寫進數組,而後寫進key爲remoteModuleConfig的字典,最後序列化成JS,造成相似這樣的json,全部的RCT開頭的都是facebook官方寫好的APIModule

{"remoteModuleConfig":[["VKAlertModule"],
["RCTFileRequestHandler"],
["RCTDataRequestHandler"],
...]}
複製代碼

moduleConfigInject的時候,會經過RCTJSExecutor,把這個json注入JSContext,在JS的global全局變量裏面加入一個__fbBatchedBridgeConfig對象,是一個數組,裏面記錄着全部APIModule的name,這樣至關於告知了JS,OC這邊有多少個APIModule分別都叫作什麼,能夠被JS調用,但此時尚未告訴JS,每個APIModule,均可以使用哪些方法

上一篇還提到了一個JS Call OC的方案,[RCTJSExecutor setUp]中設置了一大堆JSContext[「xxx」]=block的方法,這裏面有一個名爲nativeRequireModuleConfig的JSContext的block注入

當evaluateJS標記的時候,JS就會主動callnativeRequireModuleConfig這個方法,從而調用了這個blck,從名字能夠猜出來,前面咱們把全部的APIModule的名字列表發給了JS,這下JS開始用名字,找OC一一確認每個APIModule裏面都有啥具體信息,具體Method方法。

經過名字,block會找到對應的RCTModuleData,從而調用RCTModuleData-Config方法

  • 會調用RCTModuleData的methods方法拿到一個全部方法的數組
    • 運行時獲取RCTModuleData的APIModule類的全部MethodList
    • 循環找到以__rct_export__開頭的方法(上文提到過)
      • 從這個方法中獲得字符串nativeAlert:(NSString *)content xxxx
      • 截取:前面的字符串nativeAlert做爲JS簡寫方法名
      • 生成RCTModuleMethod
      • 保存在RCTModuleData內
    • 生成JS簡寫方法名數組
  • 有類名+類的簡寫方法名數組,生成APIModule的info信息,轉換成json,經過block返回給JS

["VKAlertModule",["nativeAlert"]]

這樣JS就徹底知曉,Native全部APIModule的名字,每一個APIModule下全部的Method的名字了

JS Call NA的時候

除了主動的block式callNA之外,前一篇文章提到了nativeFlushQueueImmediate這個JS主動call OC的方法,經過nativeFlushQueueImmediate的邏輯能夠看出,當JS每次想主動調用OC的時候,會把全部JS消息都扔到JS的messagequeue的隊列裏,而後每一個5毫秒,會觸發nativeFlushQueueImmediate的block,讓OC主動去發起一次flushedQueue來把這段時間內全部的JS消息都拉去過來,這就走到了前一篇文章提到過的

- (void)handleBuffer:(id)buffer batchEnded:(BOOL)batchEnded

再日後就是上文介紹過的 handleBuffer分發邏輯

  • RCTBatchedBridge-handlebuffer - analyze Buffer(analyze buffer標記) - find module(find modules標記) - for 循環all calls - dispatch async(dispatch async標記) - [RCTBatchedBridge- handleRequestNumber:] - [RCTBridgeMethod invokeWithBridge:](invocation標記 這個標記會複雜點,子流程表細說)

基本上就是,找到對應的RCTModuleData,找到對應的APIModule,找到對應的RCTModuleMethod,執行invocation,完成了調用

APIModule小結

以上就是一整個JS Call Native APIModule的源碼流程交互圖,那種API型的功能,好比系統彈框,好比社交分享,都是經過這樣的運做流程,才能讓React在JS環境中自由的調用native源生API模塊

這裏提一個遇到的坑

當咱們寫RCT_EXPORT_METHOD()宏的時候,寫導出給JS的函數的時候,若是參數中含有successcallback,errorcallback,切記把這種callback block放在最後,千萬不要,把其餘類型的參數放在block以後。

緣由是在JS代碼一側有last arg,second arg的判斷,當callbackblock 不是以倒數第二第一的位置出現的時候,JS會報exception

//正確的作法
RCT_EXPORT_METHOD(nativeAlert:(NSString *)content withButton:(NSString *)name callback:(RCTResponseSenderBlock)callback))

//錯誤的作法
RCT_EXPORT_METHOD(nativeAlert:(NSString *)content withCallback:(RCTResponseSenderBlock)callback) withButton:(NSString *)name)
複製代碼

能讓React在JS環境中自由的調用native源生API模塊,只是實現一個app很小的一部分,若是能React在JS環境中自由的建立Native源生的UI界面,自由的修改,變化每個UI界面的展示效果,纔是實現一個app最重要的一環,因而咱們進入了下一部分 源生UI組件模塊

源生UI組件模塊

什麼叫UIModule?

UIModule是一種面向對象式的UI組件調用

每個React的Component都是一個獨立的UI組件,通過React的flexbox排版計算,有本身的大小,形狀,樣式。

每個被RN渲染出來的RCTView都是繼承自UIView的純源生UI組件,他是根據React的Component的計算結果(大小,形狀,樣式)從而建立出來的。

RCTView與Component是一一對應的

當一個JS Component對象想要建立/改變本身的顏色,大小,樣式的時候,就須要經過brdige找到本身所對應的那個RCTView,傳遞過去相應的數據參數,對RCTView生效傳來的數據 JS CALL OC

當RCTView發生了觸摸等源生事件響應的時候,經過brdige找到本身所對應的JS Component,把觸摸的事件和數據參數傳過去,讓React.JS根據數據進行JS Component的從新佈局或者界面響應

我把UIModule比做iOS開發中的UIKit的子類擴展

UIModule 其實由2部分組成,RCTView與RCTViewManager,就好像v與c的關係同樣,每一個UIModule都會有一個RCTComponentData與之配合(就好像APIModule與RCTModuleData同樣)

  • UIView 對應RCTView(繼承UIView)與RCTViewManager
  • UIImage 對應RCTImageView(繼承UIImageView)與RCTImageViewManager
  • UILabel 對應RCTTextView(繼承RCTView)與RCTTextViewManager
  • UIScrolView 對應RCTScrolView(繼承RCTView)與RCTScrolViewManager

正式由於每個JS的Component都是與一個UIModule創建了一一對應的關係,因此當發生渲染的時候,JS Component的渲染信息,就會經過brdige,生成繼承自純源生iOS UIKit的UIModule

每個JS的Component與之直接配合的都是RCTViewManager,這是一個繼承自NSObject聽從RCTBridgeModule協議的類,若是你打算本身寫一個自定義的UIModule,也是須要繼承自RCTViewManager,他只是一個控制器的角色,所以RCTViewManager還須要決定他採用什麼方案進行繪製,因此在RCTViewManager能夠選擇使用不一樣的UIView來實現真正的視圖角色。

如何使用UIModule呢?

老規矩,ReactNative原生UI組件 中文文檔上面詳細介紹瞭如何使用UIModule,由於一會咱們還要詳細看源碼,我這裏還會再簡單複數一遍。因爲UIModule要處理的東西,官方文檔源碼都比較詳細了,因此我不會太細緻的介紹。

自定義一個UIModule組件

  • 建立RCTViewManager子類
    • RCT_EXPORT_MODULE()註冊宏
    • -(UIView *)view 指定視圖真正實現
    • requireNativeComponent導入JS源生組件
  • 建立屬性
    • oc 屬性導出宏(普通屬性,複雜屬性)
    • JS Component封裝 屬性propTypes聲明
  • 建立事件
    • oc定義eventblock 屬性
    • oc call eventblock
    • js component 同名function 響應
  • 建立樣式常亮(不是很經常使用)
    • oc的constantsToExport 方法返回字典
    • JS經過UIManager.XXUIModule.Constants 獲得字典

上面全都是按着文檔的流程去操做,你就能夠在JS中以React.JS的方式去構建一個純源生native組件了

...
  render: function() {
    return (
      <View style={this.props.style}> <XXUIModule ... /> </View> ); } }); 複製代碼

UIModule源碼是如何運做的

知其然知其因此然

咱們只清楚了,如何按着文檔寫一個UIModule,如何直接讓JS去使用這個UIModule對應的JS Component,但爲何會這樣,這裏面代碼是怎麼運做的,依然是一頭霧水,那麼就深刻源碼,看看這幾個宏,這幾個native oc方法是怎麼運做的

RCT_EXPORT_MODULE() 註冊Module宏

跟APIModule註冊是同一個宏,都是在一個單例RCTModuleClasses表中把xxRCTViewManager添加進去,不作多解釋

-(UIView *)view方法

RCTComponentData-createViewWithTag這個方法會調用RCTViewManager的view方法,前邊講過RCTComponentData的角色

RCTComponentData-createViewWithTag這個方法會被一個RCTUIManager調用,當真正須要渲染的時候,RCTUIManager會經過這個方式決定,到底應該alloc,init出一個什麼樣的UIView(RCTView?MKMapView?UIImageView?)

這裏提到了一個關鍵詞RCTUIManager,先按下不表,後面咱們會詳細說明

requireNativeComponent爲React.JS導入源生component

這塊就得看JS的源碼了,就是下面這個文件

node_modules/react-native/Libiraries/ReactIOS/requireNativeComponent.js

我對JS沒那麼深的瞭解,大體看了下這裏一直在操做一個叫作UIManager,從UIManager按名字取出ViewConfig的配置,而後配置了一大堆內容,那咱們就打開這個文件

node_modules/react-native/Libiraries/Utilities/UIManager.js

看到了這樣一行代碼var UIManager = require('NativeModules').UIManager;眼熟麼?沒錯,這就是APIModule在JS文件中使用的時候,須要的require,換句話說,這個UIManager操做的就是RCTUIManager,RCTUIManager先按下不表,後面咱們會詳細說明

OC屬性導出宏

  • RCT_EXPORT_VIEW_PROPERTY(name,type)
  • RCT_CUSTOM_VIEW_PROPERTY(name,type,class)
//常規導出宏
#define RCT_EXPORT_VIEW_PROPERTY(name, type) \
+ (NSArray<NSString *> *)propConfig_##name { return @[@#type]; }
//自定義導出內置宏
#define RCT_REMAP_VIEW_PROPERTY(name, keyPath, type) \
+ (NSArray<NSString *> *)propConfig_##name { return @[@#type, @#keyPath]; }
//自定義導出宏
#define RCT_CUSTOM_VIEW_PROPERTY(name, type, viewClass) \
RCT_REMAP_VIEW_PROPERTY(name, __custom__, type)         \
- (void)set_##name:(id)json forView:(viewClass *)view withDefaultView:(viewClass *)defaultView
複製代碼

看常規導出宏,##在宏裏面的用法就是字符串拼接,@#在宏裏面的用法就是參數轉字符,換句話說RCT_EXPORT_VIEW_PROPERTY(isHidden, BOOL)的做用就是生成了一個方法

+ (NSArray<NSString *> *)propConfig_isHidden { 
 	return @[@"BOOL"]; 
}
複製代碼

propConfig_isHidden這個函數被誰調用了呢?RCTComponentData的setProps:forView:方法,這個方法被誰調用了呢?RCTUIManger,嗯,一會細說

看自定義導出宏,這個宏被用來導出一些很是規類型的屬性,一些自定義的結構體,對象類型的屬性,他首先調用了自定義導出內置宏,着紅看起來和剛纔的宏差很少,只不過返回的字符串數組多了一個值,他還又單首創建了一個新函數,舉例說明,若是咱們寫了一行RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)(官方文檔的例子),就至關於自動添加了2個方法,第一個方法已經在宏裏實現了,第二個方法寫完宏後自動生成了聲明,但實現須要使用者跟着立刻補上(如同RCT_EXPORT_METHOD)

+ (NSArray<NSString *> *)propConfig_region { 
 	return @[@"MKCoordinateRegion",@"__custom__"];
}

- (void)set_region:(id)json forView:(RCTMap *)view withDefaultView:(RCTMap *)defaultView
複製代碼

第一個方法方法和常規屬性導出宏做用同樣,都會被RCTComponentData,RCTUIManger調用,詳細內容後續說明

第二個方法在哪調用呢?調用的位置牢牢挨着第一個方法執行,在第一個方法propConfig_xx執行事後,會判斷是否還有@"custom"標記,若是含有就會調用第二個方法,詳細內容仍是屬於RCTComponentData,RCTUIManger,後續會說明

至於JS Component封裝,屬性propTypes聲明,這就屬於React.JS的特性了,反正最後仍是經過JS的UIManager去操做Native

OC用屬性導出宏建立事件

一個UIView必須具有一個RCTBubblingEventBlock型的block屬性,才能夠被當作事件導出

這個block屬性導出和常規屬性導出,都是同一個宏RCT_EXPORT_VIEW_PROPERTY只不過type必須是RCTBubblingEventBlock,宏的工做流程是一致的,區別只是RCTComponentData,RCTUIManger在處理上的不一樣而已,後續說明

建立常量

這個constantsToExport方法會返回一個字典,在RCTModuleData的gatherConstants函數中被調用,而這個函數會被RCTModuleData的config方法調用

這個在APIModule的時候提到過,在injectModuleConfig的時候,獲取config,轉成json,最後會注入js,成爲js能夠獲取到得常量

RCTUIManager與RCTComponentData

終於來講RCTUIManagerRCTComponentData,上面提到了無數次,這兩個東西加上UIManager.js構成了整個RN能夠經過js建立native界面的核心

首先強調一點,RCTUIManager是一個APIModule,RCTUIManager是一個APIModule,RCTUIManager是一個APIModule,重要的事情說三遍

這個東西就有意思了,這是一個APIModule,所以就像其餘全部APIModule同樣,他會被RCTModuleData管理着,最重被RCTBatchBrdige持有着,時刻等待着JSUIManager.js的調用

可是他就像RCTBridge同樣內部也維護了不止一個字典,管理着全部的UIModule,以及全部的View,他在初始化的時候RCTUIManager-setBridge

  • 會循環全部的RCTBatchBrdige已登記在冊的Module,循環尋找其中繼承自RCTViewModule的對象
  • 錄入_componentDataByName這個內部字典表,以ModuleName爲Key
  • 此時還建立了一些其餘的表包括如下等等,這裏先不提,後面說道了會再解釋
    • _viewRegistry字典
    • _rootViewTags數組
    • _pendingUIBlocks字典

讓咱們看看,當ReactNative開始建立界面的時候,都會發生什麼事情?當進過上一篇提到的__evaluateJS標記__以後,並不會馬上開始繪製RN界面,爲啥?輸入了JSBundle之後,整個JS環境就已經徹底配置完畢,ready就位了,可是並不會真正開始繪製界面,繪製界面會經過開發者,自行建立RCTRootView,而且執行initWithBridge後開始(這裏我並無說initWithBundleURL,兩者流程是如出一轍的,可是initWithBundleURL與initWithBridge的區別我在下一環節會特別說明)

  • RCTRootView-initWithBridge
    • (若是此時JS環境已經搭建完畢) RCTRootView-bundleFinishedLoading
      • RCTContentRootView-initWithFrame:bridge:reactTag:
        • reactTag getter
          • RCTUIManager-allocateRootTag
        • RCTContentRootView-registerRootView
      • RCTContentRootView-addsubview
      • RCTRootView-runApplication
        • RCTBridge-enqueueJSCall

由於不是很長,我就不安着這標記那標記的解釋了,順着說一下。

首先建立RCTRootView的時候若是bridge已經搭建完畢,JS環境已經就位,那麼就會直接出發bundleFinishedLoading,若是JS環境沒有就位,那麼就會等待JS環境運行完畢Ready後,經過通知觸發bundleFinishedLoading

在開始正式建立RCTRootView的時候會建立一個subviewRCTContentRootView這個東西建立的時候須要一個reactTag,這個tag是一個很關鍵的東西,此時經過allocateRootTag方法建立了root得reactTag,規則是從1開始,每次建立一個RootView實例都會累加10,1,11,21,31,以此類推。建立完RCTContentRootView後還要去UIManager用這個reactTag註冊View,也就是以Tag爲Key,登記進入_viewRegistry字典表

而後將RCTContentRootView添加到RCTRootView上面,執行了runApplication,這裏面真正的意義是執行了一行JS代碼,告訴JS你要開始繪製這個參數params的界面了!

AppRegistry.runApplication(params)

再日後就是React.JS的工做了,React.JS會着手把JS中的頁面進行計算,排版,生成對應的JS Component,準備組織繪製界面了,包含着無數個JS Component的相互嵌套。最重經過UIManager.js這個APIModule的JS接口,開始call oc去建立界面

__RCTUIManager__都有哪些API提供給了JS呢?

  • createView
  • updateView
  • setChildren
  • removeRootView
  • manageChildren
  • findSubviewIn
  • measure
  • dispatchViewManagerCommand

咱們能夠想象一下,當React.JS開始工做的時候,JS把全部佈局好的Component,一層套一層的JS界面數據,經過UIManager,調用createView,updateView,setChildren等接口API,來建立一個個純iOS native的UIKit的界面。

有興趣的話,徹底能夠在runApplicationcreateViewupdateViewsetChildren等處打上斷點,看看是否是像我說的同樣。

  • createView的做用是建立一個個的UIView,RCTView,各類nativeView,而且把傳過來的JS的屬性參數,一一賦值給nativeView
  • updateView的做用是,當JSComponent的佈局信息,界面樣子發生變化,JS來通知nativeView來更新對應的屬性變化,樣子變化
  • setChildren的做用是,告訴OC,那個tag的View是另外一個tag的view的子view,須要執行addsubview,insertsubview等

這樣一來,基本上就完成了React.JS建立一個純native界面的過程,但咱們仍是具體以createView舉例,深刻分析一下這裏面的源碼,這裏面有不少咱們在前文提到的未解之謎(各類RCTComponentData去調用ViewManager的過程)

  • RCTUIManager-createView
    • js傳來viewName,經過初始化的_componentDataByName表獲取RCTComponentData
    • dispatch_async(mainqueue)從JS通訊線程拋到主線程建立UI
      • js傳來了ReactTag,經過RCTComponentData-createViewWithTag,建立界面
      • js傳來了屬性props,經過RCTComponentData-setProps:forView:進行屬性賦值
        • (只運行一次)循環每個屬性,建立屬性賦值block函數
          • 經過propConfig拼接props的名字來確認,此property是否已經使用EXPORT宏註冊過屬性(上面提到過)
          • 若是此屬性已經註冊,經過運行時invocation的方式,生成了一個block函數,每次調用這個block,就會以運行時的方式,setter給對應屬性
        • 經過屬性賦值block函數,直接輸入參數賦值屬性
      • _viewRegistry字典表裏講建立的View錄入以reactTag爲Key

看到這個過程沒有,整個過程有一個最核心的點ReactTag,爲何說這個tag核心呢?由於咱們都知道APIModule的特色就是面向過程的,他是不存在對象self這個概念的,因此必須經過這個tag,在JS裏面有一個全部JSComponent的tag表,在OC裏面依然也有這麼一個全部nativeView的Tag表_viewRegistry,只有經過惟一指定的tag,這樣APIModule-RCTUIManager,才能知道到底應該操做哪個nativeView

咱們順帶來看一看JS側的代碼吧~看看JS那邊是怎麼操做和讀取這個tag的吧~比較簡便的方式是,查JS源碼代碼,看看各處都是如何調用UIManager.js的,我對JS沒那麼熟,不細說了,怕說錯,你們能夠本身看看。

這裏要介紹一下JS那邊tag是如何管理的,有個ReactNativeTagHandles.js的JS模塊,require了ReactNativeTagHandles之後在Chrome內存中能夠看看ReactNativeTagHandles這個對象都有什麼內容,你會發現,它內部存着一大堆的表格

  • tagToRootNodeID
  • rootNodeIDToTag

你會發現他裏面存着這樣的東西

rootNodeIDToTag:Object
.r[1]{TOP_LEVEL}:1
.r[1]{TOP_LEVEL}[0]:2
.r[1]{TOP_LEVEL}[0].1:27
.r[1]{TOP_LEVEL}[0].$1:3
.r[1]{TOP_LEVEL}[0].$1.0:4
.r[1]{TOP_LEVEL}[0].$1.0.0:5
...
複製代碼

這裏面不只保存着tag信息,還保存着至關多的層級信息,還有其餘信息,簡單的發現最後一個數字大概表明着reactTag,r[n]中的那個n表明着,component所在的rootView的tag

由於這樣的內容確定不方便讀寫操做,因此ReactNativeTagHandles還提供了不少方法能夠用

  • getNativeTopRootIDFromNodeID()
  • reactTagIsNativeTopRootID()
  • balabalabal好多函數

我JS不是很熟悉,這部分就細細解讀了

由APIModule與UIModule引起的半RN半源生開發的坑

這裏就簡單的說一下,咱們由於業務緣由,註定不可能以單一RCTRootView去實現整個APP功能,註定了大部分保留現有native功能,個別動態性較強的新功能採用ReactNative去開發

因此咱們採用的是多RCTRootView得方式,什麼意思呢,建立一個RNViewController類,這個類內部有一個RCTRootView當作界面,可是整個RNViewController被當作其餘natve的UIViewControler同樣,去push,去present,去pop,並無使用ReactNative裏面的navigator組件

既然選擇了這種模式就要注意一個問題,facebook其實也在源碼的註釋中強調,若是你還有多個RCTRootView,推薦讓全部的RCTRootView共享同一個RCTBridge,畢竟上一篇文章咱們就講了,整個RCTBridge的初始化流程仍是至關的複雜,挺耗性能的,既然是一個JS環境,乾脆全部的rootview公用同一個JS環境,也就是JSBridge

這就引起了我前面提到過的,RCTRootView建立的時候,跟常規開發文檔demo都不一樣的方式initWithBridge,首先我選擇在app啓動的時候,就建立初始化整個JS環境,JSBridge,(上一篇分析過,這裏面有不少異步處理,不用擔憂卡主線程UI),等到用戶要點擊彈出RN頁面的時候,再去構建RCTRootView,使用initWithBridge的方式。

常規的開發文檔demo都選擇initWithBundleURL的方式,這個方法其實就是,把initJSBridge,與initRootView打包處理了,很適合整個app都是reactnative的開發模式,可是咱們就不適用了

從自定義的APIModule中JS call OC,OC如何知道這個JS Call來自哪一個RootView

我就碰到了這樣的一個坑,發現坑了後,讀了RCTUIManager的源碼才按着UIManager的思路解決了問題,沒錯就是reactTag,當你但願JS經過APIModule調用na的時候,在JS調用前先找到本身component所在的rootViewTag,把這個tag隨着API的參數一塊兒發過來,而後直接經過RCTBridge.uimanager的方法獲取RCTUIManager,從而查找整個_viewRegistry[tag]表,最終快速定位到,JSCall來自哪一個RootView

相關連接:ReactNative iOS源碼解析(一)

相關文章
相關標籤/搜索