react-native框架源码学习(iOS)(上)
2018-02-26
react-native框架源码学习(iOS)(上)
注意事项
要学习react-native框架iOS端源码,先要了解一下注意的事项。
- 一、 首先需要非常熟悉objective-c语言,对OC的runtime机制更是必不可少。源码里大量用了runtime特性。然后也要熟悉c++语言。除了OC语言,里面还有大量的c++代码,OC与c++混合代码。对c++的template和std库也要熟悉。我本人对c++也熟悉,但是不够深入,对一些高级特性不是很了解,所以相关的一些代码看起来很吃力。最后当然也熟悉js语言。我大学时学习过js,但是那还真是远古时代了,js近年来发展很快,我对很多新特性不是很了解,这也对我学习有一定的阻碍。另外还有要比较了解iOS的JavaScriptCore框架,这个是js与OC通信的基础。
- 二、 要对react-native的版本了解。查看网上的资料时也要注意他们所说的RN版本。现在网上很多研究资料,他们所研究的版本都是很早之前的版本。新的版本跟老版本有很多细节和流程改变了。如果你发现有些文章里说的东西跟你所看到的对不上,那就是研究的版本不一致。
- 三、如果看不懂,多看看别人的分析文章。如果对某种语言或者框架库不熟悉,建议先把该补的补上。
我所研究的RN版本是0.49,而最新版是0.51. 最后我自己也不敢说自己完全看懂了源码,自己的理解完全正确,所以下面所说的也不一定完全正确。
核心原理
主要原理利用JavaScriptCore的通信机制来做一些基本的js和OC相互调用,但是RN还做了更多事情。 OC会将所有要暴露给JS调用的方法和属性生成一个配置表,然后会将这个配置表写入到JS端。RN中OC和JS都分别有一个桥接对象,他们相互调用都是通过这个桥接对象进行。 RN大致结构图:
启动流程
研究源码当然得从启动流程开始。新建一个RN工程,它的AppDelagte入口方法代码大致如下:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"VprScene"
initialProperties:nil
launchOptions:launchOptions];
rootView.backgroundColor = [[UIColor alloc] initWithRed:0.0/255 green:0.0/255 blue:0.0/255 alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
最主要的代码就是初始化了RCTRootView,别的都是普通的代码。也就是说由RCTRootView来进行整个RN的初始化。
RCTRootView的initWithBundleURL:方法中初始化了一个RCTBridge,RCTBridge的初始化方法里调用了一个setUp方法,在这里又初始化了一个RCTCxxBridge(早期版本是RCTBatchedBridge,它们都是RCTBridge的子类)对象,然后调用了它的start方法。这是个关键方法。RCTBridge与RCTCxxBridge类似一个代理模式,RCTBridge提供了对外接口,实际上调用了RCTCxxBridge的实现。这样的好处是方便替换内部实现,而接口保持稳定。
RCTCxxBridge的start方法(为了方便理解,删掉了不必要的代码):
- (void)start
{
......
// Set up the JS thread early
_jsThread = [[NSThread alloc] initWithTarget:[self class]
selector:@selector(runRunLoop)
object:nil];
_jsThread.name = RCTJSThreadName;
_jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
[_jsThread start];
dispatch_group_t prepareBridge = dispatch_group_create();
[self registerExtraModules];
// Initialize all native modules that cannot be loaded lazily
[self _initModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
// This doesn't really do anything. The real work happens in initializeBridge.
_reactInstance.reset(new Instance);
__weak RCTCxxBridge *weakSelf = self;
// Prepare executor factory (shared_ptr for copy into block)
std::shared_ptr<JSExecutorFactory> executorFactory;
.....
if (!executorFactory) {
BOOL useCustomJSC =
[self.delegate respondsToSelector:@selector(shouldBridgeUseCustomJSC:)] &&
[self.delegate shouldBridgeUseCustomJSC:self];
// The arg is a cache dir. It's not used with standard JSC.
executorFactory.reset(new JSCExecutorFactory(folly::dynamic::object
("OwnerIdentity", "ReactNative")
("UseCustomJSC", (bool)useCustomJSC)
));
}
......
// Dispatch the instance initialization as soon as the initial module metadata has
// been collected (see initModules)
dispatch_group_enter(prepareBridge);
[self ensureOnJavaScriptThread:^{
[weakSelf _initializeBridge:executorFactory];
dispatch_group_leave(prepareBridge);
}];
.....
// Load the source asynchronously, then store it for later execution.
dispatch_group_enter(prepareBridge);
__block NSData *sourceCode;
[self loadSource:^(NSError *error, RCTSource *source) {
if (error) {
[weakSelf handleError:error];
}
sourceCode = source.data;
dispatch_group_leave(prepareBridge);
} onProgress:^(RCTLoadingProgress *progressData) {
.....
}];
// Wait for both the modules and source code to have finished loading
dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
RCTCxxBridge *strongSelf = weakSelf;
if (sourceCode && strongSelf.loading) {
[strongSelf executeSourceCode:sourceCode sync:NO];
}
});
.....
}
这个方法很复杂,主要做了以下内容:
- 创建并开启了一个js线程,绑定了一个runloop,也即是说js代码都是在这个线程里执行。
- 准备所有要暴露给js调用的OC类,ModuleClass,为每个类封装到RCTModuleData里,如果需要在主线程中是创建某些类的实例,则会在主线程中去创建它。这些RCTModuleData会分别存储在一个字典和数组里。
- 准备JS运行环境,初始化JSExecutorFactory,并在js线程中创建js的RCTMessageThread,初始化_reactInstance(Instance,这是native跟jsBridge的桥梁)和JSCExecutor
- 加载JS源码
- 以上全做完之后,执行JS源码
注意代码里用到一个叫prepareBridge的dispatch_group_t,dispatch_group_t主要是用来做异步代码同步用的。有很多初始化工作是异步并行的,运行源码肯定是在所有准备工作之后才能进行,所以用了dispatch_group_t和dispatch_group_notify机制来确保这个问题。接下来我们逐步去分析上面说的几个事情。
JS线程
大家常说JavaScript是单线程的,在RN里就是这样的。它创建了一个线程,然后绑定到了一个runloop,然后JS就是在这个线程里执行。在RCTCxxBridge里专门有个方法将block放在JS线程中执行
/**
* Ensure block is run on the JS thread. If we're already on the JS thread, the block will execute synchronously.
* If we're not on the JS thread, the block is dispatched to that thread. Any errors encountered while executing
* the block will go through handleError:
*/
- (void)ensureOnJavaScriptThread:(dispatch_block_t)block
{
RCTAssert(_jsThread, @"This method must not be called before the JS thread is created");
// This does not use _jsMessageThread because it may be called early before the runloop reference is captured
// and _jsMessageThread is valid. _jsMessageThread also doesn't allow us to shortcut the dispatch if we're
// already on the correct thread.
if ([NSThread currentThread] == _jsThread) {
[self _tryAndHandleError:block];
} else {
[self performSelector:@selector(_tryAndHandleError:)
onThread:_jsThread
withObject:block
waitUntilDone:NO];
}
}
除此之外还有个RCTMessageThread类,专门用于在js线程中执行c++的同步和异步函数。
获取ModuleClass
[self _initModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO]方法就是获取所有要暴露的类并做相应初始化。首先了解一下要将一个类暴露给JS需要怎么做。我们已RN里已经有的一个粘贴板类来看一下。
//RCTClipboard.h
@interface RCTClipboard : NSObject <RCTBridgeModule>
@end
//RCTClipboard.m
@implementation RCTClipboard
RCT_EXPORT_MODULE()
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
RCT_EXPORT_METHOD(setString:(NSString *)content)
{
UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
clipboard.string = (content ? : @"");
}
RCT_EXPORT_METHOD(getString:(RCTPromiseResolveBlock)resolve
rejecter:(__unused RCTPromiseRejectBlock)reject)
{
UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
resolve((clipboard.string ? : @""));
}
@end
其实主要关键点就是类需要实现协议RCTBridgeModule,并在implementation里加RCT_EXPORT_MODULE()。在要暴露的方法在其前面加宏RCT_EXPORT_METHOD修饰。注意这里不支持有返回值的方法,还有也可以暴露一些属性的,但是这里不展开说明。
RCTBridgeModule协议定义了+ (NSString *)moduleName这个必须实现的方法,还有其他可选方法和属性。 看一下RCT_EXPORT_MODULE()宏到底干了什么
/**
* Place this macro in your class implementation to automatically register
* your module with the bridge when it loads. The optional js_name argument
* will be used as the JS module name. If omitted, the JS module name will
* match the Objective-C class name.
*/
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
这个宏里实现moduleName方法,定义了RCT_EXTERN void RCTRegisterModule(Class),还有实现了load方法,在load方法里调用了RCTRegisterModule方法。RCTRegisterModule实现是在RCTBridge.m里面。
static NSMutableArray<Class> *RCTModuleClasses;
NSArray<Class> *RCTGetModuleClasses(void)
{
return RCTModuleClasses;
}
/**
* Register the given class as a bridge module. All modules must be registered
* prior to the first bridge initialization.
*/
void RCTRegisterModule(Class);
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];
}
实际上就是讲Class对象加入到全局唯一的一个数组里。也就是说,当系统在加载这个类的时候,就将这个类对象加入到了一个数组里,后面只要从这个数组里取所有要暴露给JS的类就可以了。
再来看看怎么暴露方法。
#define RCT_EXPORT_METHOD(method) \
RCT_REMAP_METHOD(, method)
#define RCT_REMAP_METHOD(js_name, method) \
_RCT_EXTERN_REMAP_METHOD(js_name, method, NO) \
- (void)method;
#define _RCT_EXTERN_REMAP_METHOD(js_name, method, is_blocking_synchronous_method) \
+ (const RCTMethodInfo *)RCT_CONCAT(__rct_export__, RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) { \
static RCTMethodInfo config = {#js_name, #method, is_blocking_synchronous_method}; \
return &config; \
}
这里的宏嵌套得让人眼花缭乱,在整个RN源码中运用了大量的宏,而且大多是高级用法,这个给阅读理解造成一定的困难。如果看不懂,最好先找专门的讲宏文章先看一看。这里主要作用就是声明了返回值为void的method方法,并且生成了一个以__rct_export__开头,包含了类名,行号等信息的为类名,返回值为RCTMethodInfo的类方法。这个方法主要是为后面生成配置信息用的。
[self _initModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO]主要代码是
- (void)_initModules:(NSArray<id<RCTBridgeModule>> *)modules
withDispatchGroup:(dispatch_group_t)dispatchGroup
lazilyDiscovered:(BOOL)lazilyDiscovered
{
....
// Set up moduleData for automatically-exported modules
NSArray<RCTModuleData *> *moduleDataById = [self registerModulesForClasses:modules];
......
[self _prepareModulesWithDispatchGroup:dispatchGroup];
}
registerModulesForClasses:方法主要是遍历RCTModuleClasses,为每个class生成一个RCTModuleData并在一个字典和数组里存起来后面使用。_prepareModulesWithDispatchGroup:就是检查每个RCTModuleData是否需要在主线程中创建实例,是的话,就创建实例存起来。
准备JS相关类(JSCExecutor)
在start方法里面,它实例化了Instance(_reactInstance)和一个JSExecutorFactory,然后在js线程中调用了_initializeBridge:方法。
- (void)_initializeBridge:(std::shared_ptr<JSExecutorFactory>)executorFactory
{
.....
__weak RCTCxxBridge *weakSelf = self;
_jsMessageThread = std::make_shared<RCTMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) {
if (error) {
[weakSelf handleError:error];
}
});
.....
if (_reactInstance) {
// This is async, but any calls into JS are blocked by the m_syncReady CV in Instance
_reactInstance->initializeBridge(
std::make_unique<RCTInstanceCallback>(self),
executorFactory,
_jsMessageThread,
[self _buildModuleRegistry]);
......
}
}
可以看到这里主要创建了一个RCTMessageThread,并将相关属性传给_reactInstance进行初始化。_reactInstance的初始化需要一个ModuleRegistry,ModuleRegistry里面有包含了所有的RCTNativeModule,而每个RCTNativeModule里又包含了一个RCTModuleData.即将前面生成的所有RCTModuleData传给了_reactInstance。
在Instance的初始化方法initializeBridge里,最主要是创建了NativeToJsBridge的实例。而NativeToJsBridge的构造函数里,主要是调用了executorFactory来创建一个JSCExecutor。JSCExecutor是一个相当重要的类,走到这里很不容易,跳转了很多层。这一块也是最不好理解的,基本上c++和OC互调,而且使用了很多c++11的特性,我很不熟悉。然后也使用了folly库,这个库也不是很好理解。所以我准备把剩下的东西放到下一篇文章来讲。
参考资料
理解React-Native(0.46) 中native和js通信原理(iOS)
如果你觉得这篇文章有用,请打赏小钱喝杯咖啡^_^
Category: 技术 Tagged: ReactNative 源码 iOS