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 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

@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 *RCTModuleClasses;

NSArray *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> *)modules

withDispatchGroup:(dispatch_group_t)dispatchGroup

lazilyDiscovered:(BOOL)lazilyDiscovered

{

....

// Set up moduleData for automatically-exported modules

NSArray *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)executorFactory

{

.....

__weak RCTCxxBridge *weakSelf = self;

_jsMessageThread = std::make_shared([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(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库,这个库也不是很好理解。所以我准备把剩下的东西放到下一篇文章来讲。

参考资料

如果你觉得这篇文章有用,请打赏小钱喝杯咖啡^_^

Comments

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐