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&¶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: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来实现。原生模型的函数请求通过批处理方式来执行,由于是多线程并行执行,因此执行先后顺序对于单个模块而言与请求顺序相同,但是不同模块的执行先后顺序不确定。
- React Native技术剖析(二)
- React Native技术剖析(一)
- React Native学习(二)
- React Native 入门(二)
- 初探React-native (二)
- 【React Native】React基础(二)
- React Native基础与入门(二)--初识React Native
- react native 技术栈
- react-native技术调研:react-native是什么?
- [深入剖析React Native]React Native组件之Touchable*源码解析(1)
- [深入剖析React Native]React 初探
- React Native探索(二):布局篇
- React-Native开发(二)-ListView
- React Native 学习笔记(二)
- react native (二)‘电影列表demo’
- React Native(二):属性、状态
- React Native(二) 安装编辑器 webstorm
- React Native Android(二)Navigator知识点
- PMP学习笔记之第13章 项目干系人管理 ——13.2规划干系人管理
- python3 模块构建与发布
- 23 leetcode - Generate Parentheses
- Android Retrofit框架解析
- 51Nod 1629 B君的圆锥
- React Native技术剖析(二)
- Unity 编辑器多重编辑 Multi-Object Editing
- centos6.5 安装python2.7.12
- 安卓自定义 View 进阶: 图片文字
- 面向对象1
- 创建窗口,使其能移动的代码
- CodeForces-732C-Sanatorium(模拟)
- 高性能的关键:Spring MVC的异步模式
- Android webview 加载html5 Video的视频,有时候出不来,该怎么办呢?