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&¶ms.push(this._callbackID);
this._callbacks[this._callbackID++]=onFail;
onSucc&¶ms.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技术剖析的主要内容,如果未能解决你的问题,请参考以下文章