React Native技术剖析(二)

来源:互联网 发布:淘宝官网客户端 编辑:程序博客网 时间:2024/06/01 15:48

前言

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:NSPointerFunctionsStrongMemoryvalueOptions: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 0