React Native技术剖析(二)

前言

React Native(简称RN)的由来、优势、安装使用等等不在这里啰嗦,可以自行Google/百度。笔者整理了一下之前学习RN过程中记录的笔记,结合RN源代码来分析RN框架里面的一些技术思路,这对于理解和更好地使用RN都是很有益处的。由于水平有限,肯定有一些理解不对的地方欢迎指正。
今天主要讲一下,JSC中执行原生模块的函数的过程。

JSC中原生模块函数的执行过程

在RN初始化过程中,MessageQueue对象对原生模块的函数配置信息进行处理,包装成JS函数,供后续调用;
MessageQueue对象的_genMethod函数处理函数定义如下。

_genMethod(module,method,type){
    Let fn=null;
    Let self=this;
    如果函数是远程异步函数,则给该函数创建一个Promise对象;
    if(type===MethodTypes.remoteAsync){
        fn=function(...args){
        Return new Promise((resolve,reject)=>{
        self.__nativeCall(
            module,
            method,
            args,
        (data)=>{
        resolve(data);
        },
        (errorData)=>{
        varerror=createErrorFromErrorData(errorData);
        reject(error);
    });
    });
    };
    如果是同步钩子函数,则使用global.nativeCallSyncHook创建函数;
    }elseif(type===MethodTypes.syncHook){
        Return function(...args){
            Return global.nativeCallSyncHook(module,method,args);
        }
    其余函数,则使用__nativeCall创建函数;
    }else{
        fn=function(...args){
            Let lastArg=args.length>0?args[args.length-1]:null;
            Let secondLastArg=args.length>1?args[args.length-2]:null;
            Let hasSuccCB=typeof lastArg==='function';
            Let hasErrorCB=typeof secondLastArg==='function';
            Let numCBs= hasSuccCB+hasErrorCB;
            Let onSucc= hasSuccCB?lastArg:null;
            Let onFail=hasErrorCB?secondLastArg:null;
            Args = args.slice(0,args.length-numCBs);
            Return self.__nativeCall(module,method,args,onFail,onSucc);
            };
        }
        fn.type=type;
        Return fn;
    }

可以看出,JS端调用原生模块的函数主要通过global.nativeCallSyncHook函数和MessageQueue对象的nativeCall函数来实现的。global.nativeCallSyncHook函数是原生端JSC初始化时定义的一个block函数,调用RCTBatchedBridge的 callNativeModule函数完成具体的函数执行工作, 返回值始终为空。

context[@"nativeCallSyncHook"] = ^id(NSUInteger module, NSUInteger method, NSArray *args) {
      如果JSC还没有准备就绪,则不执行
      RCTJSCExecutor *strongSelf = weakSelf;
      if (!strongSelf.valid) {
        return nil;
      }
      id result = [strongSelf->_bridge callNativeModule:module method:method params:args];
      return result;
    };

callNativeModule函数是原生端JSC初始化时定义个一个block函数,调用RCTBatchedBridge的 handleBuffer函数完成具体的函数执行工作。

- (id)callNativeModule:(NSUInteger)moduleID
                method:(NSUInteger)methodID
                params:(NSArray *)params
{
  if (!_valid) {
    return nil;
  }

根据moduleID和methodID找到对应的RCTBridgeMethod对象.这里需要仔细说明一下,每个原生模块都继承自NSObject ,其模块定义信息(或者叫元数据)记录在一个RCTModuleData对象中,RCTModuleData.methods属性在第一次访问时会通过RCTBridgeModule.methodsToExport接口函数获取当前模块导出的函数定义列表,然后将所有的函数定义信息封装为RCTBridgeMethod对象,放入内部的_methods数组中。

  RCTModuleData *moduleData = _moduleDataByID[moduleID];
  id<RCTBridgeMethod> method = moduleData.methods[methodID];

  @try {
    return [method invokeWithBridge:self module:moduleData.instance arguments:params];
  }
  @catch (NSException *exception) {
  }
}

下面看看RCTBridgeMethod对象的invokeWithBridge函数如何完成函数执行工作。函数的调用方式是采用NSInvocation来完成。

- (id)invokeWithBridge:(RCTBridge *)bridge
                module:(id)module
             arguments:(NSArray *)arguments
{

processMethodSignature函数会解析函数的_methodSignature字符串,将函数参数列表封装为RCTMethodArgument数组,然后创建一个NSInvocation对象(ObjC中直接调用对象函数的一种方式),再为每个RCTMethodArgument创建一个RCTArgumentBlock函数,其作用就是为NSInvocation对象所指的函数的调用参数赋值。如果参数类型为基本数据类型,通过RCTConvert将json对象转换为相应类型的数值即可,如果参数类型为block函数(即Promise的resolve和reject回调函数),则创建一个block函数,其中使用RCTBatchedBridge的enqueueCallback函数在JSC中执行回调函数。

  if (_argumentBlocks == nil) {
    [self processMethodSignature];
  }

  执行上面创建的RCTArgumentBlock函数,为NSInvocation对象的进行参数赋值
  NSUInteger index = 0;
  for (id json in arguments) {
    RCTArgumentBlock block = _argumentBlocks[index];
    if (!block(bridge, index, RCTNilIfNull(json))) {
      return nil;
    }
    index++;
  }

  执行函数
  [_invocation invokeWithTarget:module];

  return nil;
}

接下来看看__nativeCall函数是如何工作的.

__nativeCall(module,method,params,onFail,onSucc){
    if(onFail||onSucc){

如果提供了onFail和onSucc(针对Promise对象),则将函数放入callback数组末尾(CallbackID为回调函数唯一标示,不断累加),并将数组索引加入到params中;当原生执行返回时,根据CallbackID找到并执行相应的回调函数来处理结果。

        onFail&&params.push(this._callbackID);
        this._callbacks[this._callbackID++]=onFail;
        onSucc&&params.push(this._callbackID);
        this._callbacks[this._callbackID++]=onSucc;
    }
     调用ID,每次累积
    this._callID++;

    将原生函数调用信息(模块名、函数名和参数列表)放到queue中,后面会通过flush一次全部传递给原生端执行
    this._queue[MODULE_IDS].push(module);
    this._queue[METHOD_IDS].push(method);
    this._queue[PARAMS].push(params);

    如果上一次flush时间超过最小允许的时间间隔,则强制原生端立即执行
    varnow=newDate().getTime();
    if(global.nativeFlushQueueImmediate&&
    now-this._lastFlush>=MIN_TIME_BETWEEN_FLUSHES_MS){
    global.nativeFlushQueueImmediate(this._queue);
    this._queue=[[],[],[],this._callID];
    this._lastFlush=now;
    }
}

global.nativeFlushQueueImmediate函数是原生端JSC初始化时定义个一个block函数,调用RCTBatchedBridge的 handleBuffer函数完成具体的函数执行工作。

context[@"nativeFlushQueueImmediate"] = ^(NSArray<NSArray *> *calls){
      RCTJSCExecutor *strongSelf = weakSelf;
      if (!strongSelf.valid || !calls) {
        return;
      }
      [strongSelf->_bridge handleBuffer:calls batchEnded:NO];
    };

handleBuffer函数首先调用另一个重载的handleBuffer函数执行调用请求,然后调用partialBatchDidFlush函数。

- (void)handleBuffer:(id)buffer batchEnded:(BOOL)batchEnded
{
  if (buffer != nil && buffer != (id)kCFNull) {
    _wasBatchActive = YES;
    [self handleBuffer:buffer];
    [self partialBatchDidFlush];
  }
  if (batchEnded) {
    if (_wasBatchActive) {
      [self batchDidComplete];
    }
    _wasBatchActive = NO;
  }
}

handleBuffer函数的工作就是将列表中的函数请求按模块重新组织,对于一个模块而言,请求执行的先后顺序不会改变;但是不同模块的请求执行先后顺序则不确定,因为都是通过每个模块各自的Dispatch队列并行执行。所以,在编写原生模块时,应该注意模块的独立性,尽量不要同其他模块存在耦合关系,否则可能会导致模块函数执行结果的不确定。

- (void)handleBuffer:(NSArray *)buffer
{
  NSArray *requestsArray = [RCTConvert NSArray:buffer];
  首先将buffer中的模块ID、函数ID和参数列表分别取出来
  NSArray<NSNumber *> *moduleIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldRequestModuleIDs]];
  NSArray<NSNumber *> *methodIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldMethodIDs]];
  NSArray<NSArray *> *paramsArrays = [RCTConvert NSArrayArray:requestsArray[RCTBridgeFieldParams]];

  int64_t callID = -1;
  if (requestsArray.count > 3) {
    callID = [requestsArray[RCTBridgeFieldCallID] longLongValue];
  }

NSMapTable可以看作是一个功能更强大的NSDictionary容器. 每个原生模块的RCTModuleData对象有一个methodQueue, 这是一个串行Dispatch队列。以这个队列对象作为key,将请求数组中与该原生模块相关的数组索引按先后顺序保存下来。

  NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
valueOptions:NSPointerFunctionsStrongMemory                                                capacity:_moduleDataByName.count];
  [moduleIDs enumerateObjectsUsingBlock:^(NSNumber *moduleID, NSUInteger i, __unused BOOL *stop) {
    RCTModuleData *moduleData = self->_moduleDataByID[moduleID.integerValue];
    dispatch_queue_t queue = moduleData.methodQueue;
    NSMutableOrderedSet<NSNumber *> *set = [buckets objectForKey:queue];
    if (!set) {
      set = [NSMutableOrderedSet new];
      [buckets setObject:set forKey:queue];
    }
    [set addObject:@(i)];
  }];

然后遍历NSMapTable的key值,针对每一个原生模块的dispatch队列,创建一个block函数,函数的工作就是顺序执行该原生模块的函数请求(通过上面讲的callNativeModule函数来实现),然后dispatchBlock函数异步添加到dispatch队列中去执行

  for (dispatch_queue_t queue in buckets) {
    dispatch_block_t block = ^{
      NSOrderedSet *calls = [buckets objectForKey:queue];
      @autoreleasepool {
        for (NSNumber *indexObj in calls) {
          NSUInteger index = indexObj.unsignedIntegerValue;
          [self callNativeModule:[moduleIDs[index] integerValue]
                          method:[methodIDs[index] integerValue]
                          params:paramsArrays[index]];
        }
      }
    [self dispatchBlock:block queue:queue];
  }
}

由于是异步将请求添加到队列,因此不会等待所有请求执行完成,这时调用partialBatchDidFlush函数,就是遍历所有的原生模块,如果原生模块实现了implementsPartialBatchDidFlush接口,则将该模块的partialBatchDidFlush函数封装到一个block函数中,异步添加到dispatch队列中。这样一来,当该模块的所有请求执行完成后,就会调用partialBatchDidFlush函数。partialBatchDidFlush函数的作用就是,当一个模块的所有请求执行完毕后,但是可能其他模块的请求还没执行完,这时可能需要进行一些处理,例如RCTUIManager模块会执行flushUIBlocks去处理之前阻塞的UI相关block函数

- (void)partialBatchDidFlush
{
  for (RCTModuleData *moduleData in _moduleDataByID) {
    if (moduleData.hasInstance && moduleData.implementsPartialBatchDidFlush) {
      [self dispatchBlock:^{
        [moduleData.instance partialBatchDidFlush];
      } queue:moduleData.methodQueue];
    }
  }
}

如果本次请求列表全部执行完毕,则会调用batchDidComplete函数进行最后的处理工作,执行方式依然是封装为block函数添加到队列中执行。这个函数仅用于RCTUIManager模块, 其工作就是重新完成UI重新布局计算和更新。

- (void)batchDidComplete
{
  for (RCTModuleData *moduleData in _moduleDataByID) {
    if (moduleData.hasInstance && moduleData.implementsBatchDidComplete) {
      [self dispatchBlock:^{
        [moduleData.instance batchDidComplete];
      } queue:moduleData.methodQueue];
    }
  }
}

小结

JS端调用原生模块的函数最终都是通过RCTBatchedBridge的 callNativeModule函数来完成, 执行方式是采用 NSInvocation来实现。原生模型的函数请求通过批处理方式来执行,由于是多线程并行执行,因此执行先后顺序对于单个模块而言与请求顺序相同,但是不同模块的执行先后顺序不确定。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值