遷移老文章到掘金(基於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
(說明,官方文檔把這個起名就叫源生模塊,英文Module,我這裏先中二的起名叫APIModule,爲了和另外一個區別起名一下,瞎起的名字,你們湊合一下)github
(說明,官方文檔把這個起名就叫源生UI組件,英文Component,我這裏先中二的起名叫UIModule,爲了和另外一個區別起名,瞎起的名字,你們湊合一下)json
API模塊闡述了JS是如何調用native各個模塊的邏輯react-native
UI組件闡述了JS是如何建立出native的UI界面
本文在源碼分析部分,對照前文的代碼流程能夠加深理解
什麼叫APIModule?
APIModule是一種面向過程式的模塊調用
JS只須要用一個模塊名,一個API名,就能經過bridge找到對應的native的方法進行調用,JS Call OC Method
這個過程就是一個函數調用而已,不存在操做某個實例對象,只是傳遞參數,操做參數,處理邏輯,返回數值 OC Call JS(經過前文知道,bridge都是異步的,經過callback block返回)
舉個例子好了,對於系統alert彈框,分享微信朋友圈,這種功能是最適合使用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,如何直接讓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的時候,就會從單例RCTModuleClasses表中拿出全部的APIModule的class對象,循環去建立RCTModuleData實例(上文提到過RCTModuleData不是APIModule,而是包裝了一下APIModule,RCTModuleData.instance纔是APIModule),而且一一保存在RCTBatchBridge對象的三個表中
+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方法
__rct_export__
開頭的方法(上文提到過)
nativeAlert:(NSString *)content xxxx
nativeAlert
做爲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,完成了調用
以上就是一整個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組件模塊
什麼叫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 其實由2部分組成,RCTView與RCTViewManager,就好像v與c的關係同樣,每一個UIModule都會有一個RCTComponentData與之配合(就好像APIModule與RCTModuleData同樣)
正式由於每個JS的Component都是與一個UIModule創建了一一對應的關係,因此當發生渲染的時候,JS Component的渲染信息,就會經過brdige,生成繼承自純源生iOS UIKit的UIModule
每個JS的Component與之直接配合的都是RCTViewManager,這是一個繼承自NSObject聽從RCTBridgeModule協議的類,若是你打算本身寫一個自定義的UIModule,也是須要繼承自RCTViewManager,他只是一個控制器的角色,所以RCTViewManager還須要決定他採用什麼方案進行繪製,因此在RCTViewManager能夠選擇使用不一樣的UIView來實現真正的視圖角色。
老規矩,ReactNative原生UI組件 中文文檔上面詳細介紹瞭如何使用UIModule,由於一會咱們還要詳細看源碼,我這裏還會再簡單複數一遍。因爲UIModule要處理的東西,官方文檔源碼都比較詳細了,因此我不會太細緻的介紹。
自定義一個UIModule組件
-(UIView *)view
指定視圖真正實現上面全都是按着文檔的流程去操做,你就能夠在JS中以React.JS的方式去構建一個純源生native組件了
...
render: function() {
return (
<View style={this.props.style}> <XXUIModule ... /> </View> ); } }); 複製代碼
知其然知其因此然
咱們只清楚了,如何按着文檔寫一個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屬性導出宏
//常規導出宏
#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
,上面提到了無數次,這兩個東西加上UIManager.js
構成了整個RN能夠經過js建立native界面的核心
首先強調一點,RCTUIManager是一個APIModule,RCTUIManager是一個APIModule,RCTUIManager是一個APIModule,重要的事情說三遍
這個東西就有意思了,這是一個APIModule,所以就像其餘全部APIModule同樣,他會被RCTModuleData管理着,最重被RCTBatchBrdige持有着,時刻等待着JSUIManager.js
的調用
可是他就像RCTBridge同樣內部也維護了不止一個字典,管理着全部的UIModule,以及全部的View,他在初始化的時候RCTUIManager-setBridge
_componentDataByName
這個內部字典表,以ModuleName爲Key讓咱們看看,當ReactNative開始建立界面的時候,都會發生什麼事情?當進過上一篇提到的__evaluateJS標記__以後,並不會馬上開始繪製RN界面,爲啥?輸入了JSBundle之後,整個JS環境就已經徹底配置完畢,ready就位了,可是並不會真正開始繪製界面,繪製界面會經過開發者,自行建立RCTRootView,而且執行initWithBridge後開始(這裏我並無說initWithBundleURL,兩者流程是如出一轍的,可是initWithBundleURL與initWithBridge的區別我在下一環節會特別說明)
由於不是很長,我就不安着這標記那標記的解釋了,順着說一下。
首先建立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呢?
咱們能夠想象一下,當React.JS開始工做的時候,JS把全部佈局好的Component,一層套一層的JS界面數據,經過UIManager,調用createView,updateView,setChildren等接口API,來建立一個個純iOS native的UIKit的界面。
有興趣的話,徹底能夠在runApplication
,createView
,updateView
,setChildren
等處打上斷點,看看是否是像我說的同樣。
這樣一來,基本上就完成了React.JS建立一個純native界面的過程,但咱們仍是具體以createView舉例,深刻分析一下這裏面的源碼,這裏面有不少咱們在前文提到的未解之謎(各類RCTComponentData去調用ViewManager的過程)
propConfig
拼接props的名字來確認,此property是否已經使用EXPORT宏註冊過屬性(上面提到過)看到這個過程沒有,整個過程有一個最核心的點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這個對象都有什麼內容,你會發現,它內部存着一大堆的表格
你會發現他裏面存着這樣的東西
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還提供了不少方法能夠用
我JS不是很熟悉,這部分就細細解讀了
這裏就簡單的說一下,咱們由於業務緣由,註定不可能以單一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