React Native技术剖析

Posted 伪装狙击手

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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来实现。原生模型的函数请求通过批处理方式来执行,由于是多线程并行执行,因此执行先后顺序对于单个模块而言与请求顺序相同,但是不同模块的执行先后顺序不确定。

以上是关于React Native技术剖析的主要内容,如果未能解决你的问题,请参考以下文章

React Native技术剖析

React Native技术剖析

React Native技术剖析

[深入剖析React Native]手势响应讲解

[深入剖析React Native]React 初探

React native for Android [原理剖析]