今年3月Facebook开源的ReactNative框架吸引了国内外许许多多开发者的关注,国内关注程度最高的莫过于天猫的前端团队,他们甚至做了一些根据自身需求的改进,这阵子的D2前端大会里有谈到一些,感兴趣的同学可自行google一下。
ReactNative的优缺点知乎讨论地特别多了,我这里就不妄加评论。目前用起这套框架,一些简单的功能可以很快实现,特别是component的做法。但是一涉及到网络图片缓存,手势交互,循环利用tableView,ReactNative恐怕就爱莫能助了,还好之前积累了一些代码,只要稍加改动然后通过RCT_EXPORT_METHOD暴露到JS端就可以使用了。
只使用RN框架(ReactNative缩写,下同)怎满足得了程序猿的好奇心,用JS写的时候总会想着底层如何实现,今天得空看了一下底层的部分代码,算是一个概览,关于RCTBridge的建立流程的。
看代码的思路一般都是顺藤摸瓜,不过有时候摸着摸着就回不去了,迷失在瓜田中。所以最好的做法是先通读一遍,不深入细节,根据注释了解每一部分实现什么功能,总之就是要有大概的印象,后面要深究时再一部分一部分进行。注意本文谈到的RN框架是0.16版本的。
1.RCTBridge简介
可参考bang的通信机制,看完那一篇之后会对整个bridge有大概的了解。
2.RCTBridge建立流程
2.1初始化
在AppDelegate.m中RCTBridge的初始化方法是
_bridge= [[RCTBridgealloc]initWithDelegate:self
launchOptions:launchOptions];
这里设置代理Appdelegate类为RctBridge的代理,会执行什么方法呢,后文会谈到。
进入此函数后,可以看到下面这个函数。
- (instancetype)initWithDelegate:(id)delegate
launchOptions:(NSDictionary*)launchOptions
{
RCTAssertMainThread();
if((self= [superinit])) {
RCTPerformanceLoggerStart(RCTPLTTI);
_delegate= delegate;
_launchOptions= [launchOptionscopy];
[selfsetUp];
[selfbindKeys];
}
returnself;
}
首先是进行主线程的一个检测,确保当前在主线程中。然后是开启性能记录器,传值,接着就是建立bridge和绑定键盘操作了。
2.2建立过程
在setup方法里,出现了一个RCTConver,估计是facebook自己写的转换器,用于生成一个_bundleURL,然后就是初始化一个RCTBatchedBridge,注意这里的self.batchedBridge是RCTBridge实例而不是RCTBatchedBridge实例,在这里的是多态的用法。
- (void)setUp
{
RCTAssertMainThread();
_bundleURL= [self.delegatesourceURLForBridge:self] ?:_bundleURL;
// Sanitize the
bundle URL
_bundleURL= [RCTConvertNSURL:_bundleURL.absoluteString];
self.batchedBridge= [[RCTBatchedBridgealloc]initWithParentBridge:self];
}
初始化过后
- (void)start
{
dispatch_queue_tbridgeQueue =dispatch_queue_create("com.facebook.react.RCTBridgeQueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_group_tinitModulesAndLoadSource =dispatch_group_create();
//
Asynchronously load source code
dispatch_group_enter(initModulesAndLoadSource);
__weakRCTBatchedBridge*weakSelf =self;
__blockNSData*sourceCode;
[selfloadSource:^(NSError*error,NSData*source) {
if(error) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelfstopLoadingWithError:error];
});
}
sourceCode = source;
dispatch_group_leave(initModulesAndLoadSource);
}];
//
Synchronously initialize all native modules that cannot be loaded lazily
[selfinitModules];
#if
RCT_DEBUG
_syncInitializedModules = [[_moduleDataByID
valueForKeyPath:@"@sum.hasInstance"] integerValue];
#endif
if(RCTProfileIsProfiling()) {
//
Depends on moduleDataByID being loaded
RCTProfileHookModules(self);
}
__blockNSString*config;
dispatch_group_enter(initModulesAndLoadSource);
dispatch_async(bridgeQueue, ^{
dispatch_group_tsetupJSExecutorAndModuleConfig =dispatch_group_create();
//
Asynchronously initialize the JS executor
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
[weakSelfsetUpExecutor];
});
//
Asynchronously gather the module config
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
if(weakSelf.isValid) {
RCTPerformanceLoggerStart(RCTPLNativeModulePrepareConfig);
config = [weakSelfmoduleConfig];
RCTPerformanceLoggerEnd(RCTPLNativeModulePrepareConfig);
#if
RCT_DEBUG
NSIntegertotal =
[[_moduleDataByID valueForKeyPath:@"@sum.hasInstance"] integerValue];
_asyncInitializedModules= total -_syncInitializedModules;
#endif
}
});
dispatch_group_notify(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
//
We're not waiting for this to complete to leave dispatch group, since
//
injectJSONConfiguration and executeSourceCode will schedule operations
//
on the same queue anyway.
RCTPerformanceLoggerStart(RCTPLNativeModuleInjectConfig);
[weakSelfinjectJSONConfiguration:configonComplete:^(NSError*error) {
RCTPerformanceLoggerEnd(RCTPLNativeModuleInjectConfig);
if(error) {
dispatch_async(dispatch_get_main_queue(),
^{
[weakSelfstopLoadingWithError:error];
});
}
}];
dispatch_group_leave(initModulesAndLoadSource);
});
});
dispatch_group_notify(initModulesAndLoadSource,dispatch_get_main_queue(), ^{
RCTBatchedBridge*strongSelf = weakSelf;
if(sourceCode && strongSelf.loading) {
dispatch_async(bridgeQueue, ^{
[weakSelfexecuteSourceCode:sourceCode];
});
}
});
}
这个方法挺长,看起来会比较懵。不过一点点看下来还是能看懂的。首先是声明一个bridgeQueue,这个在后面会用到。然后就是声明initModulesAndLoadSource组,看到dispatch_group_t时可以知道这里会做一些线程相关的操作了,特别是多任务并发全部完成后再执行其他任务。这几个task的执行顺序还挺有意思的。
首先
异步加载源码、建立JS执行器和模块配置信息是并发的
只有完成了这两步之后才会异步执行源码。
初始化所有未延迟的本地模块是在主线程完成的。
而建立JS执行器和模块配置信息又分三步,异步初始化JS执行器,异步收集模块配置信息,这两步做完后才进行第三部注入JSON配置信息。
这么一个流程下来后就建立了RCTBridge
2.3键盘操作绑定
键盘操作主要是在模拟器上调试可以直接用快捷键Command+ r来进行热加载刷新界面。
RCTKeyCommands*commands = [RCTKeyCommandssharedInstance];
// reload in
current mode
[commandsregisterKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(__unusedUIKeyCommand*command)
{
[[NSNotificationCenterdefaultCenter]postNotificationName:RCTReloadNotification
object:nil
userInfo:nil];
}];
更新到Xcode7之后有一些朋友没法使用Command+r来刷新估计就是这里执行时没反应。