本文将从React Native的整体架构、通信机制、具体交互细节来深入介绍React Native在iOS平台上的工作原理。有不对之处,还望指点。
一、整体架构介绍
React提供了一套基于JS/JSX的UI库,而React Native是基于React可以生成Native控件的框架。
我总结是两层,Native层提供了JS-OCBridge,与JavaScript进行交互。JavaScript中提供了一些列与Native一一对应的API。 注:网上看到有些是提供了Javascript Bridge,因为没有研究React代码,有待以后了解。
二、初始化流程
初始化流程牵涉几个部分:
1、加载JS代码(RCTJavaScriptLoader)
在RCTBatchedBridge的- (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad中,调用RCTJavaScriptLoader加载相关数据:
else if (self.bundleURL) { [RCTJavaScriptLoaderloadBundleAtURL:self.bundleURLonComplete:onSourceLoad]; }
2、配置native module(RCTBatchedBridge moduleConfig)
React Native通过以下方式可以把native 接口提供给JS调用:
1)继承 RCTBridgeModule,如
@interfaceCalendarManager :NSObject
2)调用宏RCT_EXPORT_MODULE() & RCT_EXPORT_METHOD() ,如
RCT_EXPORT_MODULE();RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location){RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);}
其中,RCT_EXPORT_METHOD是把接口封装成 “__rct_export__”+ “__COUNTER__”形式,COUNTER是数字累加值,从0开始。
3)所有的module都保存在RCTModuleMap中,每个module通过遍历method中是否包含__rct_export__前缀来获取公开给JS的接口
unsigned int methodCount;
Method *methods = class_copyMethodList(object_getClass(_moduleClass), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
IMP imp = method_getImplementation(method);
NSArray *entries = ((NSArray *(*)(id, SEL))imp)(_moduleClass, selector);
id moduleMethod =
[[RCTModuleMethod alloc] initWithObjCMethodName:entries[1]
JSMethodName:entries[0]
moduleClass:_moduleClass];
[moduleMethods addObject:moduleMethod];
}
}
3、配置JavaScript运行环境(RCTJavaScriptExecutor)
在iOS中有两种JS运行环境,分别是UIWebView(iOS7以下)和 JSGlobalContext(ios7及其以上)。在RCTJavaScriptExecutor setup进行创建,
if(!strongSelf->_context) { JSGlobalContextRef ctx = JSGlobalContextCreate(NULL); strongSelf->_context =[[RCTJavaScriptContext alloc]initWithJSContext:ctx];
}
4、把API列表注入JS环境中(injectJSONConfiguration)
在moduleConfig中生成一个JSON内容,注入到JS执行环境中。这个JSON内容如下:
“CalendarManager":{"methods":{"addEvent":{"type":"remote","methodID":0},"findEvents":{"type":"remote","methodID":1}},"moduleID":46},"RCTPickerManager":{"constants":{"ComponentWidth":320,"ComponentHeight":216},"moduleID":47,"methods":{}},…. ….
可以看到,每个module都有一个ID对应,如CalendarManager的moduleID是46;每个方法也有一个methodID对应。
二、通信机制
在网上看到一个交互图,基本描述整个过程是如何进展的:
JS和OC之间、以及各个组件之间交互都是通过RCTEventDispatcher来传递的。 以点击为例:
1、在RCTTouchHandler模块,把参数封装后传递[RCTBridge enqueueJSCall:args:]在JSThread中执行。
具体是存储在RCTBatchedBridge的_scheduledCalls中,然后触发[RCTBatchedBridge updateJSDisplayLinkState],由CADisplayLink的回调方法[RCTBatchedBridge_jsThreadUpdate]执行_scheduledCalls中所有操作。
2、经过处理后,会回调[RCTBathedBridge _handleBuffer],把参数buffer转成module名称、方法名和参数,并在该module所对应的queue进行回调处理。
idmethod= moduleData.methods[methodID]; @try{ [methodinvokeWithBridge:self module:moduleData.instance arguments:params]; }
3、如果要回调JS,则通过[RCTEventDispatcher sendAppEventWithName:body:]发送消息。
四、多线程的作用
1、JavascriptThread:”com.facebook.React.JavaScript”, 与RCTContextExecutor关联,JS方法的处理和调用都在该线程处理。
2、RCTBridgeQueue:”com.facebook.react.RCTBridgeQueue”,处理OC对JS提供的接口,如遍历接口、封装在moduleData中等。
3、Module Queue:每个module都有一个queue,串行执行。不过为什么要在每个module自己的queue来执行?
4、Shadow Queue: ”com.facebook.React.ShadowQueue”,主要处理RCTUIManager相关业务;
五、框架可能的问题
1、启动耗时&内存:RN是把所有js打成一个bundle,一次加载并执行,所以对启动耗时和内存都会有影响。
2、JSContext的消耗问题:看到网上有人提到JSContext消耗比较大,还需要继续研究。
六、有待研究的内容
1、CADisplayLink在RN(React Native)是如何处理业务的;
2、JS参数到Native API参数的转换;
3、扩展能力:JS如何调用RN没有的组件?
参考: