从ios到react-native程序人生

react-native从入门到放弃(2)从ios端来看RN运行

2017-05-09  本文已影响113人  显生宙

打开我们刚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)

上一篇下一篇

猜你喜欢

热点阅读