iOS 开源库源码分析之react-native

此文基于 react-native-0.53.0

react-native 是 facebook 于 2015.3.27 发布的一款构建移动 native app 的框架,使用的 JavaScript 和 React。

本文主要讲述 react-native 在 iOS 端的内容,包括 react-native 的加载流程,JS 与 native 互调的过程,UI 渲染的步骤以及如何传递 native 事件。

加载流程

  • RCTBridge 的初始化
  • batchedBridge(RCTCxxBridge) 调用 start
  • 通过 RCTJavaScriptLoader 的 loadBundleAtURL 方法把 url 解析成 data
  • 获取到数据后,调用 RCTCxxBridge 的 executeApplicationScript
  • 调用 JSCExecutor 的 loadApplicationScript 方法
  • 在 loadApplicationScript 里面会调用 JSCHelplers 的 evaluateScript 方法,传入 JSContextRef 和前面的 data 转化成的脚本字符串 script,以及 sourceURL,这里其实最终调用的就是 JavaScriptCore 的 JSBase 类的 JSEvaluateScript 函数

补充RCTBridge的几个通知的时机

  • RCTJavaScriptWillStartLoadingNotification

RCTCxxBridge 调用start的时候

  • RCTJavaScriptWillStartExecutingNotification ,表示bridge开始执行JS bundle

RCTCxxBridge调用executeApplicationScript的时候

  • RCTJavaScriptDidLoadNotification , 表示bridge 完成load JS bundle

RCTJavaScriptLoader loadBundleAtURL 成功之后调用

  • RCTJavaScriptDidFailToLoadNotification,表示bridge load JS bundle 失败

RCTJavaScriptLoader loadBundleAtURL 失败之后调用

  • RCTBridgeWillDownloadScriptNotification ,表示bridge开始下载一个script

RCTJavaScriptLoader loadBundleAtURL 调用前发出该通知

  • RCTBridgeDidDownloadScriptNotification

RCTJavaScriptLoader loadBundleAtURL完成之后调用,在RCTJavaScriptDidLoadNotification之前

顺序是 RCTJavaScriptWillStartLoadingNotification -> RCTBridgeWillDownloadScriptNotification -> RCTBridgeDidDownloadScriptNotification -> RCTJavaScriptWillStartExecutingNotification -> RCTJavaScriptDidLoadNotification

在调用start方法里面,在loadBundleAtURL之前

[self _initModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];

这里会注册所有的bridgemodule

JS调用native方法

native 方法能够被 JS 调用

  • 一个 native 的类能够被访问首先需要注册进入 JS,是通过一个宏 RCT_EXPORT_MODULE(js_name),会把 js_name(类名)加入在 RCTBridge 的 RCTModuleClasses(数组,模块配置列表)中
  • 然后会把 RCTModuleClasses 转化为 RCTCxxBridge 的 _moduleDataByID (_moduleDataByID 的元素是RCTModuleData类,里面有methods数组)
  • 接着在 RCTCxxBridge 的 buildModuleRegistry 中会构建出 ModuleRegistry 对象,传入 _moduleDataByID 作为其成员变量 modules

  • Objective-C 的对象能够被 JS 访问得益于此,

installGlobalProxy(m_context, "nativeModuleProxy",
                           exceptionWrapMethod<&jscexecutor::getnativemodule>());
最终这里会调用 JSObjectSetProperty(),传入类名和访问的 callback。 这样 JS 端在通过
var RCTAlertManager = require('NativeModules').AlertManager;

访问的时候就会回调 JSCExecutor 的 getNativeModule 方法,最终调用 JSCNativeModules 的 getModule,在这里会从 mmoduleRegistry->getConfig(name) 获取对应的结果,ModuleRegistry 的查找就是根据前面modules模块配置列表找到对应的 RCTAlertManager

  • ObjC 的方法能够被 JS 调用则是这样,JSCHelpers 的 installGlobalFunction(),可以设置 JS 的 function callback,JS 可以同步(在 JS 线程里面)或者异步(切换到指定线程)调用 native 方法,分别是 nativeCallSyncHook 和 nativeFlushQueueImmediate(MessageQueue.js 的 enqueueNativeCall 调用 callback 对象),这两个方法定义在NativeModules.js,callback 实现在 native 端

    • nativeFlushQueueImmediate 这个方法主要是将js传过来的队列里面的需要调用的方法一起调用了。nativeCallSyncHook 这个方式主要是给JS直接同步调用ObjC方法来用的。

installNativeHook 最终是把 exceptionWrapMethod(nativeCallSyncHook) callback设置到对应的 jsName(nativeCallSyncHook) 上去,这里通过的是 JSC_JSObjectMakeFunctionWithCallback 方法(也就是 JSObjectRef 的JSObjectMakeFunctionWithCallback())

installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");

void JSCExecutor::installNativeHook(const char* name) {
  installGlobalFunction(m_context, name, exceptionWrapMethod<method>());
}

void installGlobalFunction(
    JSGlobalContextRef ctx,
    const char* name,
    JSObjectCallAsFunctionCallback callback) {
  String jsName(ctx, name);
  JSObjectRef functionObj = JSC_JSObjectMakeFunctionWithCallback(
    ctx, jsName, callback);
  Object::getGlobalObject(ctx).setProperty(jsName, Value(ctx, functionObj));
}

对于 exceptionWrapMethod 里面会把 installNativeHook 传进来的执行者和方法来调用 (executor->*method)(argumentCount, arguments),这样调用 callback 的时候就会调用 JSCExecutor 的 nativeCallSyncHook 或者 nativeFlushQueueImmediate 方法。

  template&lt;JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])&gt;
  inline JSObjectCallAsFunctionCallback exceptionWrapMethod() {
    struct funcWrapper {
      static JSValueRef call(
                             JSContextRef ctx,
                             JSObjectRef function,
                             JSObjectRef thisObject,
                             size_t argumentCount,
                             const JSValueRef arguments[],
                             JSValueRef *exception) {
        try {
          auto executor = Object::getGlobalObject(ctx).getPrivate<JSCExecutor>();
          if (executor && executor->getJavaScriptContext()) { // Executor not invalidated
            return (executor->*method)(argumentCount, arguments);
          }
        } catch (...) {
          *exception = translatePendingCppExceptionToJSError(ctx, function);
        }
        return Value::makeUndefined(ctx);
      }
    };

    return &funcWrapper::call;
  }
  • nativeFlushQueueImmediate 或者 nativeCallSyncHook 会调用 ModuleRegistry 的 callNativeMethod/callSerializableNativeHook 方法,在这里会去匹配模块列表,然后进行正确调用,之后调用 RCTNativeModule 的 invokeInner 方法

  • 最后调用 RCTModuleMethod 类的 [method invokeWithBridge:bridge module:moduleData.instance arguments:objcParams]; 这里其实调用的是 runtime 的 [_invocation invokeWithTarget:module];

native调用JS方法

  • native 可以调用 RCTBridge的enqueueJSCall:args: 方法来调用 JS 环境的代码
  • 然后会调用到 NativeToJsBridge.cpp 的 callFunction 方法
  • 然后调用 JSCExecutor 的 callFunction 方法
  • 最后在 Value.cpp 中调用下面
Value Object::callAsFunction(JSObjectRef thisObj, int nArgs, const JSValueRef args[]) const {
  JSValueRef exn;
  JSValueRef result = JSC_JSObjectCallAsFunction(m_context, m_obj, thisObj, nArgs, args, &exn);

  if (!result) {
    throw JSException(m_context, exn, "Exception calling object as function");
  }
  return Value(m_context, result);
}

JSC_JSObjectCallAsFunction这里其实是通过__jsc_wrapper最终调用了JSObjectRef的JSObjectCallAsFunction函数。

RCTMessageThread

JS 是单线程的,所以在 native 里面调用 JS 或者 JS 调用 native 的回调都是在JS 线程的。

  • 在 RCTBridge 初始化的时候会使用 NSThread 初始化出_jsThread实例。
  • 之后通过 ensureOnJavaScriptThread 方法,在_jsThread中初始化_jsMessageThread(RCTMessageThread)
  • 在通过 RCTCxxBridge 的 executeApplicationScript 加载 js 文件的时候,最终也会在 NativeToJsBridge 的 loadApplication 方法里面调用 m_executorMessageQueueThread->runOnQueue,来切换到_jsMessageThread。

  • native 调用 JS 时,在 enqueueJSCall 里面 ensureOnJavaScriptThread 来进行切换线程

  • JS 通过 native 定义的 CallSyncHook 和 nativeFlushQueueImmediate 同步或者异步调用 native 方法时候都是会在 RCTMessageThread 线程回调,如果同步则直接调用,如果异步(RCTNativeModule::invoke)则会切换到 moduledata 指定的线程

moduledata是可以指定执行线程的,详见其类的setUpMethodQueue方法

if (!_methodQueue && _bridge.valid) {
  // Create new queue (store queueName, as it isn't retained by dispatch_queue)
  _queueName = [NSString stringWithFormat:@"com.facebook.react.%@Queue", self.name];
  _methodQueue = dispatch_queue_create(_queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
}

UI渲染

  • 在最开始 RCTUImanager 设置 bridge 的时候会给_componentDataByName赋值,这是一个以 componentData.name 为 key 的字典,保存了所有为 RCTViewManager 子类的 moduleClasses

  • RCTRootView 在初始化的时候,这里会把 RCTRootContentView addSubView,然后会调用 JS 端 AppRegistry 的 runApplication 方法

  • 这个时候会收到 RootView 的 batchDidComplete 消息进行 RootView 的布局相关操作

  • 之后 JS 会调用 native RCTUIManager 的 createView: 方法进行子视图的创建

在 create 方法里面会形成一个_shadowViewRegistry字典,key 为所要创建 view 的 reactTag,值为 view 对应的 RCTShadowView

然后在主线程形成一个_viewRegistry字典,key 同样为 reactTag,值则是对应的 UIView

  • 当收到 batchDidComplete 的消息的时候,会进行子视图的 UI 布局的工作,在这里会通过 addUIBlock 把 UI 工作加入_pendingUIBlocks,最后通过 flushUIBlocksWithCompletion 把所有_pendingUIBlocks异步到主线程来做

  • 在 flushUIBlocksWithCompletion 里面,首先会 setChildren 的 UIBlock,RCTUIManager 的 RCTSetChildren() 方法这里会根据 reactTag 通过 UIView+React 的 insertReactSubview 进行排序,之后还会设置 UIView 的相关属性,然后调用其 didUpdateReactSubviews 来 addSubView。

touch和scroll事件处理

react-native 对于 touch 和 scroll 事件是通过 RCTEventDispatcher 来处理的

  • 当 native 接收到对应的事件以后,会通过 RCTEventDispatcher 的 sendEvent 来处理,这个时候会把 eventID 加入_eventQueue,_events则保存eventID对应的event

在这里如果还有未处理的事件后先 block 住,当之前的处理完之后会 dispatch 到 RCTJSThread 来处理

  • 处理事件调用的是 flushEventsQueue,这里会通过 enqueueJSCall 来发送到 JS 端

说实话,跨平台开发一直是开发者热爱所在,有移动端的跨平台 react-native,有 PC 端的跨平台 electron,这些都是通过 JS 就可以实现跨平台的,当然也有其他语言也有,不过都不如 JS 应用广泛。跨平台可以减少成本,加快迭代,不用发版,再加上移动互联网的发展,JS 语言的魅力,所以说 react-native 才可以大放异彩。本文只是 react-native 的冰山一角,感兴趣的朋友可以多多了解。

本文作者coderyi

results matching ""

    No results matching ""