美文网首页
ReactNative IOS 运行原理之

ReactNative IOS 运行原理之

作者: 耗子_wo | 来源:发表于2021-01-31 01:50 被阅读0次

    这一期我们来介绍下ReactNative IOS 运行原理之<OC TO JavaScript>,老规矩我们不做每个细节的介绍,我们只介绍调用的实现原理,首先介绍OC TO JavaScript端的调用过程:

    1 . OC TO JavaScript 首先先上图大致流程:

    我们不做过多的介绍只介绍说红框的部分,也就是OC TO JavaScript的主要调用部分

    image.png
    - (void)runApplication:(RCTBridge *)bridge
    {
      NSString *moduleName = _moduleName ?: @"";
      NSDictionary *appParameters = @{
        @"rootTag" : _contentView.reactTag,
        @"initialProps" : _appProperties ?: @{},
      };
       // 调用RCTCxxBridge的enqueueJSCall:method:args:completion:方法
      [bridge enqueueJSCall:@"AppRegistry" method:@"runApplication" args:@[ moduleName, appParameters ] completion:NULL];
    }
    

    enqueueJSCall就是OC TO JavaScript的调用,大家先记住这个函数,首先我们从头开始,首先初始化的时候 JSIExecutor 中执行 flush 函数;flush 函数中会在首次时对 JS 和 native 进行绑定;在绑定之后 native 就可以调用 JS 函数,实现 OC TO JavaScript 之间的通信。绑定规则如下:

    // 各种js方法向native的绑定
    void JSIExecutor::bindBridge() {
      std::call_once(bindFlag_, [this] {
        // 通过js侧的__fbBatchedBridge获取对应的batchedBridge
        Value batchedBridgeValue =
            runtime_->global().getProperty(*runtime_, "__fbBatchedBridge");
        if (batchedBridgeValue.isUndefined()) {
          throw JSINativeException(
              "Could not get BatchedBridge, make sure your bundle is packaged correctly");
        }
    // 把batchedBridge中的callFunctionReturnFlushedQueue 和 JSIExecutor对象的callFunctionReturnFlushedQueue_进行绑定
        Object batchedBridge = batchedBridgeValue.asObject(*runtime_);
        callFunctionReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
            *runtime_, "callFunctionReturnFlushedQueue");
      // 把batchedBridge中的invokeCallbackAndReturnFlushedQueue 和 JSIExecutor中的invokeCallbackAndReturnFlushedQueue_进行绑定;
        invokeCallbackAndReturnFlushedQueue_ = batchedBridge.getPropertyAsFunction(
            *runtime_, "invokeCallbackAndReturnFlushedQueue");
      // 把batchedBridge中的flushedQueue 和 JSIExecutor中的flushedQueue_进行绑定。
        flushedQueue_ =
            batchedBridge.getPropertyAsFunction(*runtime_, "flushedQueue");
      });
    }
    
    

    __fbBatchedBridge 其实就是一个全局的 MessageQueue对象
    在BatchedBridge.js里面如下:

    const MessageQueue = require('./MessageQueue');
    
    const BatchedBridge: MessageQueue = new MessageQueue();
    
    // Wire up the batched bridge on the global object so that we can call into it.
    // Ideally, this would be the inverse relationship. I.e. the native environment
    // provides this global directly with its script embedded. Then this module
    // would export it. A possible fix would be to trim the dependencies in
    // MessageQueue to its minimal features and embed that in the native runtime.
    
    Object.defineProperty(global, '__fbBatchedBridge', {
      configurable: true,
      value: BatchedBridge,
    });
    
    module.exports = BatchedBridge;
    

    其中绑定的时候 batchedBridge.getPropertyAsFunction 是对于JavaScriptCore函数的封装而已,本质上还是调用了JavaScriptCore的API来实现的,这里是获取到了JS端的callFunctionReturnFlushedQueue函数指针保存到了callFunctionReturnFlushedQueue_里面准备进行OC TO JavaScript的调用

    getPropertyAsFunctiond 方法的调用介绍如下:

    Function Object::getPropertyAsFunction(Runtime& runtime, const char* name)
        const {
      Object obj = getPropertyAsObject(runtime, name);
      if (!obj.isFunction(runtime)) {
        throw JSError(
            runtime,
            std::string("getPropertyAsFunction: property '") + name + "' is " +
                kindToString(std::move(obj), &runtime) + ", expected a Function");
      };
    
      return std::move(obj).getFunction(runtime);
    }
    
    
    
    Object Object::getPropertyAsObject(Runtime& runtime, const char* name) const {
      Value v = getProperty(runtime, name);
    
      if (!v.isObject()) {
        throw JSError(
            runtime,
            std::string("getPropertyAsObject: property '") + name + "' is " +
                kindToString(v, &runtime) + ", expected an Object");
      }
    
      return v.getObject(runtime);
    }
    
    inline Value Object::getProperty(Runtime& runtime, const char* name) const {
      return getProperty(runtime, String::createFromAscii(runtime, name));
    }
    
    inline Value Object::getProperty(Runtime& runtime, const String& name) const {
      return runtime.getProperty(*this, name);
    }
    

    // 最终调用了这个JavaScriptCore API的这个函数 JSObjectGetProperty

    jsi::Value JSCRuntime::getProperty(
        const jsi::Object &obj,
        const jsi::String &name) {
      JSObjectRef objRef = objectRef(obj);
      JSValueRef exc = nullptr;
      JSValueRef res = JSObjectGetProperty(ctx_, objRef, stringRef(name), &exc);
      checkException(exc);
      return createValue(res);
    }
    

    最终调用了这个JavaScriptCore API的这个函数 JSObjectGetProperty 去获取到JS代码的函数映射到OC代码里面,用来作为OC函数触发调用,关于JavaScriptCore的介绍我们打算再分一期来重点介绍这部分的内容

    对于 bridge enqueueJSCall, RN会着 Instance->NativeToJsBridge->JSIExecutor 这个调用链调用了 JSIExecutor::callFunction 方法,方法内调用了 JSIExecutor 的 callFunctionReturnFlushedQueue_方法。
    在 bindBridge 中callFunctionReturnFlushedQueue_是通过 runtime 的方式将 native 的callFunctionReturnFlushedQueue_指向了 js 中的callFunctionReturnFlushedQueue函数的。
    native 将 moduleId、methodId、arguements 作为参数执行 JS 侧的 callFunctionReturnFlushedQueue 函数,函数会返回一个 queue;这个 queue 就是 JS 需要 native 侧执行的方法;最后 native 侧交给callNativeModules去执行对应的方法。
    js 侧使用 callFunction 获取到指定的 module 和 method;使用 apply 执行对应方法。

    // RCTxxBridge.mm
    
    - (void)enqueueJSCall:(NSString *)module
                   method:(NSString *)method
                     args:(NSArray *)args
               completion:(dispatch_block_t)completion{
        if (strongSelf->_reactInstance) {
            // 调用了Instance.callJSFunction
          strongSelf->_reactInstance->callJSFunction(
              [module UTF8String], [method UTF8String], convertIdToFollyDynamic(args ?: @[]));
        }
      }];
    }
    
    // Instance.cpp
    void Instance::callJSFunction(
        std::string &&module,
        std::string &&method,
        folly::dynamic &&params) {
      callback_->incrementPendingJSCalls();
      // 调用NativeToJsBridge的callFunction
      nativeToJsBridge_->callFunction(
          std::move(module), std::move(method), std::move(params));
    }
    
    // NativeToJsBridge.cpp
    void NativeToJsBridge::callFunction(
        std::string &&module,
        std::string &&method,
        folly::dynamic &&arguments) {
      runOnExecutorQueue([this,
                          module = std::move(module),
                          method = std::move(method),
                          arguments = std::move(arguments),
                          systraceCookie](JSExecutor *executor) {
        // 调用了JSIExecutor中的callFunction
        executor->callFunction(module, method, arguments);
      });
    }
    
    // JSIExecutor.cpp
    
    void JSIExecutor::callFunction(
        const std::string &moduleId,
        const std::string &methodId,
        const folly::dynamic &arguments) {
    // 如果还未将callFunctionReturnFlushedQueue_和js函数中的callFunctionReturnFlushedQueue函数进行绑定,那么首先进行绑定
      if (!callFunctionReturnFlushedQueue_) {
        bindBridge();
      }
    
      Value ret = Value::undefined();
      try {
        scopedTimeoutInvoker_(
            [&] {
            // 调用callFunctionReturnFlushedQueue_  传入JS moduleId、methodId、arguements 参数,JS侧会返回queue
              ret = callFunctionReturnFlushedQueue_->call(
                  *runtime_,
                  moduleId,
                  methodId,
                  valueFromDynamic(*runtime_, arguments));
            },
            std::move(errorProducer));
      } catch (...) {
    
      }
    // 执行native modules
      callNativeModules(ret, true);
    }
    
    // MessageQueue.js
    
     callFunctionReturnFlushedQueue(
        module: string,
        method: string,
        args: any[],
      ): null | [Array<number>, Array<number>, Array<any>, number] {
        this.__guard(() => {
          this.__callFunction(module, method, args);
        });
    
        return this.flushedQueue();
      }
    
        __callFunction(module: string, method: string, args: any[]): void {
        this._lastFlush = Date.now();
        this._eventLoopStartTime = this._lastFlush;
        const moduleMethods = this.getCallableModule(module);
        moduleMethods[method].apply(moduleMethods, args);
      }
    
    

    最终掉用到了 MessageQueue.js 里面的 callFunctionReturnFlushedQueue函数,里面使用JavaScript的apply语法来执行这个模块方法的调用
    moduleMethods[method].apply(moduleMethods, args); 来执行JS函数

    其中在AppRegistry.js里面对于这个JS模块进行了注册

    //AppRegistry.js
    BatchedBridge.registerCallableModule(‘AppRegistry’, AppRegistry);
    

    MessageQueue.js 里面保存到了_lazyCallableModules数组里面

    //MessageQueue.js
      registerCallableModule(name: string, module: Object) {
        this._lazyCallableModules[name] = () => module;
      }
    

    __callFunction里面的 this.getCallableModule实际上就是按照名字取出了这个模块,然后进行调用

    //MessageQueue.js
      getCallableModule(name: string): any | null {
        const getValue = this._lazyCallableModules[name];
        return getValue ? getValue() : null;
      }
    
      __callFunction(module: string, method: string, args: any[]): void {
        this._lastFlush = Date.now();
        this._eventLoopStartTime = this._lastFlush;
    
        //省略···
    
        const moduleMethods = this.getCallableModule(module);
    
        //省略···
    
        moduleMethods[method].apply(moduleMethods, args);
        Systrace.endEvent();
      }
    

    这样就完成了OC TO JavaScript的调用,总结如果OC TO JavaScript端的调用的话总体来说源头就是enqueueJSCall方法:

    // RCTxxBridge.mm
    
    - (void)enqueueJSCall:(NSString *)module
                   method:(NSString *)method
                     args:(NSArray *)args
               completion:(dispatch_block_t)
    
    

    我们来总结一下调用流程:

    • native 执行完成 js 代码会发送一个RCTJavaScriptDidLoadNotification时间给 RCTRootView;

    • RCTRootView 接收时间后会使用batchedBridge->enqueueJSCall去执行AppRegistry.runApplication函数;启动 RN 页面。

    • 执行enqueueJSCall的过程会沿着Instance->NativeToJsBridge->JSIExecutor 这个调用链调用了 JSIExecutor::callFunction 方法,方法内调用了 JSIExecutor 的 callFunctionReturnFlushedQueue_方法。

    • callFunctionReturnFlushedQueue_由于已经和 JS 侧的 callFunctionReturnFlushedQueue 方法已经绑定,所以在执行此 js 函数时会执行 callFunction 方法,使用 js 的 apply 函数执行module.methodName 的调用。

    值得一提的是官方推荐OC TO JavaScript使用RCTEventEmitter来实现,首先继承自他,实现如下:

    // CalendarManager.h
    #import <React/RCTBridgeModule.h>
    #import <React/RCTEventEmitter.h>
    
    @interface CalendarManager : RCTEventEmitter <RCTBridgeModule>
    
    @end
    
    

    然后通过 sendEventWithName 触发调用,其中这里面也是用到了enqueueJSCall:这个方法,大家可以细细回味一下,是不是很多东西都有共通性呢?

    - (void)sendEventWithName:(NSString *)eventName body:(id)body
    {
      RCTAssert(
          _bridge != nil || _invokeJS != nil,
          @"Error when sending event: %@ with body: %@. "
           "Bridge is not set. This is probably because you've "
           "explicitly synthesized the bridge in %@, even though it's inherited "
           "from RCTEventEmitter.",
          eventName,
          body,
          [self class]);
    
      if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) {
        RCTLogError(
            @"`%@` is not a supported event type for %@. Supported events are: `%@`",
            eventName,
            [self class],
            [[self supportedEvents] componentsJoinedByString:@"`, `"]);
      }
      if (_listenerCount > 0 && _bridge) {
        [_bridge enqueueJSCall:@"RCTDeviceEventEmitter"
                        method:@"emit"
                          args:body ? @[ eventName, body ] : @[ eventName ]
                    completion:NULL];
      } else if (_listenerCount > 0 && _invokeJS) {
        _invokeJS(@"RCTDeviceEventEmitter", @"emit", body ? @[ eventName, body ] : @[ eventName ]);
      } else {
        RCTLogWarn(@"Sending `%@` with no listeners registered.", eventName);
      }
    }
    

    细心的你应该发现一个问题就是 callFunctionReturnFlushedQueue 执行的时候还执行了 return this.flushedQueue(); 去返回一个队列给Native端,这个队列有个什么作用呢,等下节我们讲到Javascript TO OC的时候为大家揭晓答案

    好了OC TO JavaScript部分就到这里了,下一篇介绍JavaScript TO OC的调用原理···

    相关文章

      网友评论

          本文标题:ReactNative IOS 运行原理之

          本文链接:https://www.haomeiwen.com/subject/lzqxtltx.html