react-native从入门到放弃(2)从ios端来看RN运行
打开我们刚init 的 demo
目录分析一下他的实现原理
React Native用iOS自带的JavaScriptCore作为JS的解析引擎,但并没有用到JavaScriptCore提供的一些可以让JS与OC互调的特性,而是自己实现了一套机制,这套机制可以通用于所有JS引擎上,在没有JavaScriptCore的情况下也可以用webview代替,实际上项目里就已经有了用webview作为解析引擎的实现,应该是用于兼容iOS7以下没有JavascriptCore的版本。
普通的JS-OC通信实际上很简单,OC向JS传信息有现成的接口,像webview提供的-stringByEvaluatingJavaScriptFromString方法可以直接在当前context上执行一段JS脚本,并且可以获取执行后的返回值,这个返回值就相当于JS向OC传递信息。React Native也是以此为基础,通过各种手段,实现了在OC定义一个模块方法,JS可以直接调用这个模块方法并还可以无缝衔接回调。
RN 与 Objective-C 之间的通信
Objective-C 和 JavaScript 两端都保存了一份配置表,里面标记了所有 Objective-C 暴露给 JavaScript 的模块和方法。这样,无论是哪一方调用另一方的方法,实际上传递的数据只有 ModuleId、MethodId 和 Arguments
这三个元素,它们分别表示类、方法和方法参数,当 Objective-C 接收到这三个值后,就可以通过 runtime 唯一确定要调用的是哪个函数,然后调用这个函数。
这里有一个模块配置表的概念
首先OC要告知JS它有什么模块,模块内的方法,参数。JS知道后才可能去调用这些方法。
OC端和JS端分别各有一个bridge,两个bridge都保存了同样一份模块配置表,JS调用OC模块方法时,通过bridge里的配置表把模块方法转为模块ID和方法ID传给OC,OC通过bridge的模块配置表找到对应的方法执行
闭包与回调
既然说到函数互调,那么就不得不提到回调了。对于 Objective-C 来说,执行完 JavaScript 代码再执行 Objective-C 回调毫无难度,难点依然在于 JavaScript 代码调用 Objective-C 之后,如何在 Objective-C 的代码中,回调执行 JavaScript 代码。
目前 React Native 的做法是:在 JavaScript 调用 Objective-C 代码时,注册要回调的 Block,并且把 BlockId
作为参数发送给 Objective-C,Objective-C 收到参数时会创建 Block,调用完 Objective-C 函数后就会执行这个刚刚创建的 Block。
Objective-C 会向 Block 中传入参数和 BlockId
,然后在 Block 内部调用 JavaScript 的方法,随后 JavaScript 查找到当时注册的 Block 并执
看完原理我们来看一下React Native的源码,我的RN Version 是0.44
首先程序入口在AppDelegate中,我去掉了部分源码,我们只看有用的
NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"AwesomeProject"
initialProperties:nil
launchOptions:launchOptions];
进入 jsBundleURLForBundleRoot 方法我们来看一下内部的实现
- (NSURL *)jsBundleURLForBundleRoot:(NSString *)bundleRoot fallbackResource:(NSString *)resourceName
{
resourceName = resourceName ?: @"main";
NSString *packagerServerHost = [self packagerServerHost];
if (!packagerServerHost) {
return [[NSBundle mainBundle] URLForResource:resourceName withExtension:@"jsbundle"];
} else {
NSString *path = [NSString stringWithFormat:@"/%@.bundle", bundleRoot];
// When we support only iOS 8 and above, use queryItems for a better API.
NSString *query = [NSString stringWithFormat:@"platform=ios&dev=%@&minify=%@",
[self enableDev] ? @"true" : @"false",
[self enableMinification] ? @"true": @"false"];
return [[self class] resourceURLForResourcePath:path packagerHost:packagerServerHost query:query];
}
}
这个方法创建了一个本地的js文件访问URL.
继续向下走
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
launchOptions:(NSDictionary *)launchOptions
{
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL
moduleProvider:nil
launchOptions:launchOptions];
return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
}
initWithBundleURL参数分析
**bundleURL : 初始化是我们创建的URL
moduleProvider : 此块可用于实例化需要额外的模块(这一块我们后期再深入的了解,先留一个疑问,暂时不传)
launchOptions : 加载的参数**
注意
此时我们的线程是保持在主线程中执行,在setUp方法中
void RCTExecuteOnMainQueue(dispatch_block_t block)
{
if (RCTIsMainQueue()) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), ^{
block();
});
}
}
首先创建了一个RCTBridge对象,通过文档我们知道,它是一个异步批量链接JavaScript应用程序通信。
Async batched bridge used to communicate with the JavaScript application.
RCTBridge对象起到了桥接两端的作用,具体实现要在RCTBatchedBridge 类中的 start方法内, start中执行了重要的初始化任务,也是RN的精髓,基于大量的GCD实现了异步初始化组件框架的任务.
- (void)start
{
//将要开始加载的通知
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTJavaScriptWillStartLoadingNotification
object:_parentBridge userInfo:@{@"bridge": self}];
//为事件收集初始事件信息并返回引用ID
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge setUp]", nil);
dispatch_queue_t bridgeQueue = dispatch_queue_create("com.facebook.react.RCTBridgeQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t initModulesAndLoadSource = dispatch_group_create();
// Asynchronously load source code
dispatch_group_enter(initModulesAndLoadSource);
__weak RCTBatchedBridge *weakSelf = self;
__block NSData *sourceCode;
// RCTMultipartDataTask 类做网络请求, 基于NSURLSession, 获得javascript data
[self loadSource:^(NSError *error, NSData *source, __unused int64_t sourceLength) {
if (error) {
RCTLogWarn(@"Failed to load source: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
}
sourceCode = source;
dispatch_group_leave(initModulesAndLoadSource);
} onProgress:^(RCTLoadingProgress *progressData) {
#if RCT_DEV && __has_include("RCTDevLoadingView.h")
RCTDevLoadingView *loadingView = [weakSelf moduleForClass:[RCTDevLoadingView class]];
[loadingView updateProgress:progressData];
#endif
}];
// 同步初始化所有不能被懒惰加载的本机模块
// Synchronously initialize all native modules that cannot be loaded lazily
[self initModulesWithDispatchGroup:initModulesAndLoadSource];
RCTPerformanceLogger *performanceLogger = self->_performanceLogger;
__block NSString *config;
dispatch_group_enter(initModulesAndLoadSource);
dispatch_async(bridgeQueue, ^{
dispatch_group_t setupJSExecutorAndModuleConfig = dispatch_group_create();
// Asynchronously initialize the JS executor
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
[performanceLogger markStartForTag:RCTPLJSCExecutorSetup];
[weakSelf setUpExecutor];
[performanceLogger markStopForTag:RCTPLJSCExecutorSetup];
});
// Asynchronously gather the module config
dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
if (weakSelf.valid) {
RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge moduleConfig", nil);
[performanceLogger markStartForTag:RCTPLNativeModulePrepareConfig];
config = [weakSelf moduleConfig];
[performanceLogger markStopForTag:RCTPLNativeModulePrepareConfig];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
});
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.
//利用 javaScriptCore 引擎为 native 设置方法和属性
[performanceLogger markStartForTag:RCTPLNativeModuleInjectConfig];
[weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) {
[performanceLogger markStopForTag:RCTPLNativeModuleInjectConfig];
if (error) {
RCTLogWarn(@"Failed to inject config: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
}
}];
dispatch_group_leave(initModulesAndLoadSource);
});
});
//拿到请求后的javascript data 进行解码
dispatch_group_notify(initModulesAndLoadSource, bridgeQueue, ^{
RCTBatchedBridge *strongSelf = weakSelf;
if (sourceCode && strongSelf.loading) {
[strongSelf executeSourceCode:sourceCode];
}
});
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
基本流程图
RN启动流程.png启动流程部分大概就这样,理解了从加载URL/local path 到 通过 jsCore引擎来加载到应用层的基本逻辑,后面继续探索他的原理以及其他的使用技巧,理解原理后才能更好的去使用RN
下面就是将RN应用到自己的项目中开始AwesomeProject 之旅
有问题欢迎评论指正,我会及时进行修改。
参考文章
React Native 知识库
React Native通信机制详解
React Native 从入门到原理
React Native运行原理解析 android
React-Native 与 iOS原生项目间通信(已有项目添加RN)