注册Modules的流程和注册Components非常类似。

+ (void)_registerDefaultModules
{
[self registerModule:@"dom" withClass:NSClassFromString(@"WXDomModule")];
[self registerModule:@"locale" withClass:NSClassFromString(@"WXLocaleModule")];
[self registerModule:@"navigator" withClass:NSClassFromString(@"WXNavigatorModule")];
[self registerModule:@"stream" withClass:NSClassFromString(@"WXStreamModule")];
[self registerModule:@"animation" withClass:NSClassFromString(@"WXAnimationModule")];
[self registerModule:@"modal" withClass:NSClassFromString(@"WXModalUIModule")];
[self registerModule:@"webview" withClass:NSClassFromString(@"WXWebViewModule")];
[self registerModule:@"instanceWrap" withClass:NSClassFromString(@"WXInstanceWrap")];
[self registerModule:@"timer" withClass:NSClassFromString(@"WXTimerModule")];
[self registerModule:@"storage" withClass:NSClassFromString(@"WXStorageModule")];
[self registerModule:@"clipboard" withClass:NSClassFromString(@"WXClipboardModule")];
[self registerModule:@"globalEvent" withClass:NSClassFromString(@"WXGlobalEventModule")];
[self registerModule:@"canvas" withClass:NSClassFromString(@"WXCanvasModule")];
[self registerModule:@"picker" withClass:NSClassFromString(@"WXPickerModule")];
[self registerModule:@"meta" withClass:NSClassFromString(@"WXMetaModule")];
[self registerModule:@"webSocket" withClass:NSClassFromString(@"WXWebSocketModule")];
[self registerModule:@"voice-over" withClass:NSClassFromString(@"WXVoiceOverModule")];
}

WXSDKEngine会默认注册这17种基础模块。这里以模块WXWebSocketModule为例,来看看它是如何被注册的。

+ (void)registerModule:(NSString *)name withClass:(Class)clazz
{
WXAssert(name && clazz, @"Fail to register the module, please check if the parameters are correct !");
if (!clazz || !name) {
return;
}
// 1. WXModuleFactory注册模块
NSString *moduleName = [WXModuleFactory registerModule:name withClass:clazz];
// 2.遍历所有同步和异步方法
NSDictionary *dict = [WXModuleFactory moduleMethodMapsWithName:moduleName];
// 3.把模块注册到WXBridgeManager中
[[WXSDKManager bridgeMgr] registerModules:dict];
}

我们逐步来分析注册模块的三个过程。

第一步:在WXModuleFactory中注册。

@interface WXModuleFactory ()

@property (nonatomic, strong)  NSMutableDictionary  *moduleMap;
@property (nonatomic, strong) NSLock *moduleLock; @end

在WXModuleFactory中,moduleMap会存储所有模块的配置信息,注册的过程也是生成moduleMap的过程。

- (NSString *)_registerModule:(NSString *)name withClass:(Class)clazz
{
WXAssert(name && clazz, @"Fail to register the module, please check if the parameters are correct !"); [_moduleLock lock];
//allow to register module with the same name;
WXModuleConfig *config = [[WXModuleConfig alloc] init];
config.name = name;
config.clazz = NSStringFromClass(clazz);
[config registerMethods]; //同注册组件的方法
[_moduleMap setValue:config forKey:name];
[_moduleLock unlock]; return name;
}

整个注册的过程就是把WXModuleConfig为value,name为key,存入_moduleMap字典里。

@interface WXModuleConfig : WXInvocationConfig

@end

@interface WXInvocationConfig : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *clazz;
/**
* The methods map
**/
@property (nonatomic, strong) NSMutableDictionary *asyncMethods;
@property (nonatomic, strong) NSMutableDictionary *syncMethods; @end

WXModuleConfig仅仅是继承自WXInvocationConfig,所以它和WXInvocationConfig是完全一样的。[config registerMethods]这个方法和注册组件的方法是同一个方法,具体注册流程这里就不多说了。

经过注册,在WXModuleFactory中会记录下一个个的WXModuleConfig:

_moduleMap = {
animation = "<WXModuleConfig: 0x60000024a230>";
canvas = "<WXModuleConfig: 0x608000259ce0>";
clipboard = "<WXModuleConfig: 0x608000259b30>";
dom = "<WXModuleConfig: 0x608000259440>";
event = "<WXModuleConfig: 0x60800025a280>";
globalEvent = "<WXModuleConfig: 0x60000024a560>";
instanceWrap = "<WXModuleConfig: 0x608000259a70>";
meta = "<WXModuleConfig: 0x60000024a7a0>";
modal = "<WXModuleConfig: 0x6080002597d0>";
navigator = "<WXModuleConfig: 0x600000249fc0>";
picker = "<WXModuleConfig: 0x608000259e60>";
storage = "<WXModuleConfig: 0x60000024a4a0>";
stream = "<WXModuleConfig: 0x6080002596e0>";
syncTest = "<WXModuleConfig: 0x60800025a520>";
timer = "<WXModuleConfig: 0x60000024a380>";
webSocket = "<WXModuleConfig: 0x608000259fb0>";
webview = "<WXModuleConfig: 0x6080002598f0>";
}

每个WXModuleConfig中会记录下所有的同步和异步的方法:

config.name = dom,
config.clazz = WXDomModule,
config.asyncMethods = {
addElement = "addElement:element:atIndex:";
addEvent = "addEvent:event:";
addRule = "addRule:rule:";
createBody = "createBody:";
createFinish = createFinish;
getComponentRect = "getComponentRect:callback:";
moveElement = "moveElement:parentRef:index:";
refreshFinish = refreshFinish;
removeElement = "removeElement:";
removeEvent = "removeEvent:event:";
scrollToElement = "scrollToElement:options:";
updateAttrs = "updateAttrs:attrs:";
updateFinish = updateFinish;
updateStyle = "updateStyle:styles:";
},
config.syncMethods = { }

Moudle 中的方法注册比 Component 更有意义,因为 Moudle 中基本上都是暴露给 Vue 调用的 Native 方法。

第二步:遍历所有的方法列表。

NSDictionary *dict = [WXModuleFactory moduleMethodMapsWithName:moduleName];

- (NSMutableDictionary *)_moduleMethodMapsWithName:(NSString *)name
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSMutableArray *methods = [self _defaultModuleMethod]; [_moduleLock lock];
[dict setValue:methods forKey:name]; WXModuleConfig *config = _moduleMap[name];
void (^mBlock)(id, id, BOOL *) = ^(id mKey, id mObj, BOOL * mStop) {
[methods addObject:mKey];
};
[config.syncMethods enumerateKeysAndObjectsUsingBlock:mBlock];
[config.asyncMethods enumerateKeysAndObjectsUsingBlock:mBlock];
[_moduleLock unlock]; return dict;
}

从上面的源码可以看到,遍历模块的方法列表和组件的有所不同:

一:模块是有默认方法的。

- (NSMutableArray*)_defaultModuleMethod
{
return [NSMutableArray arrayWithObjects:@"addEventListener",@"removeAllEventListeners", nil];
}

所有的模块都有addEventListener和removeAllEventListeners方法。

二:模块会遍历所有的同步和异步方法(组件只会遍历异步方法),最终返回生成模块的所有方法的字典。例如dom模块,返回的字典如下:

{
dom = (
addEventListener,
removeAllEventListeners,
addEvent,
removeElement,
updateFinish,
getComponentRect,
scrollToElement,
addRule,
updateAttrs,
addElement,
createFinish,
createBody,
updateStyle,
removeEvent,
refreshFinish,
moveElement
);
}

第三步:在WXBridgeManager注册模块。

[[WXSDKManager bridgeMgr] registerModules:dict];

- (void)registerModules:(NSDictionary *)modules
{
if (!modules) return; __weak typeof(self) weakSelf = self;
WXPerformBlockOnBridgeThread(^(){
[weakSelf.bridgeCtx registerModules:modules];
});
} - (void)registerModules:(NSDictionary *)modules
{
WXAssertBridgeThread(); if(!modules) return; [self callJSMethod:@"registerModules" args:@[modules]];
}

这里注册过程和组件是完全一样的,也是在子线程@"com.taobao.weex.bridge"的jsThread中操作的。

只是调用JS的方法名变为了@"registerModules",入参args就是第二步产生的方法字典。

args =     (
{
dom = (
addEventListener,
removeAllEventListeners,
addEvent,
removeElement,
updateFinish,
getComponentRect,
scrollToElement,
addRule,
updateAttrs,
addElement,
createFinish,
createBody,
updateStyle,
removeEvent,
refreshFinish,
moveElement
);
}
)

附录:Moudle 的方法如何被调用?

在前面的Components讲解的最后,jsBridge懒加载中,有一个注册方法是跟 Moudle 中方法有关的,Moudle 中的方法会在这个注册方法的回调中被 invoke,换言之,Vue 调用 Moudle 中的方法会在这个回调中被唤起:

[_jsBridge registerCallNativeModule:^NSInvocation*(NSString *instanceId, NSString *moduleName, NSString *methodName, NSArray *arguments, NSDictionary *options) {

        WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];

        if (!instance) {
WXLogInfo(@"instance not found for callNativeModule:%@.%@, maybe already destroyed", moduleName, methodName);
return nil;
} WXModuleMethod *method = [[WXModuleMethod alloc] initWithModuleName:moduleName methodName:methodName arguments:arguments options:options instance:instance];
if(![moduleName isEqualToString:@"dom"] && instance.needPrerender){
[WXPrerenderManager storePrerenderModuleTasks:method forUrl:instance.scriptURL.absoluteString];
return nil;
}
return [method invoke];
}];

WXModuleMethod中可以看到-(NSInvocation *)invoke这个方法,Moudle 中的方法将会通过这个方法被 invoke:

- (NSInvocation *)invoke
{
if (self.instance.needValidate) {
id<WXValidateProtocol> validateHandler = [WXHandlerFactory handlerForProtocol:@protocol(WXValidateProtocol)];
if (validateHandler) {
WXModuleValidateResult* result = nil;
if ([validateHandler respondsToSelector:@selector(validateWithWXSDKInstance:module:method:args:options:)]) {
result = [validateHandler validateWithWXSDKInstance:self.instance module:self.moduleName method:self.methodName args:self.arguments options:self.options];
} if (result==nil || !result.isSuccess) {
NSString *errorMessage = [result.error.userInfo objectForKey:@"errorMsg"];
WXLogError(@"%@", errorMessage);
WX_MONITOR_FAIL(WXMTJSBridge, WX_ERR_INVOKE_NATIVE, errorMessage);
if ([result.error respondsToSelector:@selector(userInfo)]) {
// update the arguments when validate failed, so update the arguments for invoking -[NSError userInfo].
if ([self respondsToSelector:NSSelectorFromString(@"arguments")]) {
[self setValue:nil forKey:@"arguments"];
}
NSInvocation *invocation = [self invocationWithTarget:result.error selector:@selector(userInfo)];
[invocation invoke];
return invocation;
}else{
return nil;
}
}
}
} Class moduleClass = [WXModuleFactory classWithModuleName:_moduleName];
if (!moduleClass) {
NSString *errorMessage = [NSString stringWithFormat:@"Module:%@ doesn't exist, maybe it has not been registered", _moduleName];
WX_MONITOR_FAIL(WXMTJSBridge, WX_ERR_INVOKE_NATIVE, errorMessage);
return nil;
} id<WXModuleProtocol> moduleInstance = [self.instance moduleForClass:moduleClass];
WXAssert(moduleInstance, @"No instance found for module name:%@, class:%@", _moduleName, moduleClass);
BOOL isSync = NO;
SEL selector = [WXModuleFactory selectorWithModuleName:self.moduleName methodName:self.methodName isSync:&isSync]; if (![moduleInstance respondsToSelector:selector]) {
// if not implement the selector, then dispatch default module method
if ([self.methodName isEqualToString:@"addEventListener"]) {
[self.instance _addModuleEventObserversWithModuleMethod:self];
} else if ([self.methodName isEqualToString:@"removeAllEventListeners"]) {
[self.instance _removeModuleEventObserverWithModuleMethod:self];
} else {
NSString *errorMessage = [NSString stringWithFormat:@"method:%@ for module:%@ doesn't exist, maybe it has not been registered", self.methodName, _moduleName];
WX_MONITOR_FAIL(WXMTJSBridge, WX_ERR_INVOKE_NATIVE, errorMessage);
}
return nil;
} [self commitModuleInvoke];
NSInvocation *invocation = [self invocationWithTarget:moduleInstance selector:selector]; if (isSync) {
[invocation invoke];
return invocation;
} else {
[self _dispatchInvocation:invocation moduleInstance:moduleInstance];
return nil;
}
}

先通过 WXModuleFactory 拿到对应的方法 Selector,然后再拿到这个方法对应的 NSInvocation ,最后 invoke 这个 NSInvocation。对于 syncMethods 和 asyncMethods 有两种 invoke 方式。如果是 syncMethod 会直接 invoke ,如果是 asyncMethod,会将它派发到某个指定的线程中进行 invoke,这样做的好处是不会阻塞当前线程。到这里 Moudle 的大概的运行原理都清楚了,不过还有一个问题,Moudle 的方法是怎么暴露给 Vue 的呢?

在 Moudle 中我们通过 Weex 提供的宏可以将方法暴露出来:

#define WX_EXPORT_METHOD(method) WX_EXPORT_METHOD_INTERNAL(method,wx_export_method_)

#define WX_EXPORT_METHOD_SYNC(method) WX_EXPORT_METHOD_INTERNAL(method,wx_export_method_sync_)

分别提供了 syncMethod 和 asyncMethod 的宏,展开其实是这样的:

#define WX_EXPORT_METHOD_INTERNAL(method, token) \
+ (NSString *)WX_CONCAT_WRAPPER(token, __LINE__) { \
return NSStringFromSelector(method); \
}
//这里会自动将方法名和当前的行数拼成一个新的方法名,这样做的好处是可以保证方法的唯一性,例如 `WXDomModule` 中的 `createBody:` 方法利用宏暴露出来,最终展开形式是这样的
+ (NSString *)wx_export_method_40 {
return NSStringFromSelector(createBody:);
} //在`WXInvocationConfig`中调用`- (void)registerMethods`注册方法的时候,首先拿到当前 class 中所有的类方法**(宏包装成的方法,并不是实际要注册的方法)**,然后通过判断有无`wx_export_method_sync_`前缀和`wx_export_method_`前缀来判断是否为暴露的方法,然后再调用该类方法,获得最终的实例方法字符串
method = ((NSString* (*)(id, SEL))[currentClass methodForSelector:selector])(currentClass, selector);

拿到需要注册的实例方法字符串,再将方法字符串注册到WXInvocationConfig的对应方法 map 中。

WeexSDK之注册Modules的更多相关文章

  1. WeexSDK之注册Components

    先来看一下注册Components的源码: + (void)_registerDefaultComponents { [self registerComponent:@"container& ...

  2. WeexSDK之注册Handlers

    先看代码: + (void)_registerDefaultHandlers { [self registerHandler:[WXResourceRequestHandlerDefaultImpl ...

  3. WeexSDK源码分析(iOS)

    0.从工作原理谈起 Weex 表面上是一个客户端技术,但实际上它串联起了从本地开发.云端部署到分发的整个链路.开发者首先可在本地像编写 web 页面一样编写一个 app 的界面,然后通过命令行工具将之 ...

  4. ASP.NET PipeLine #Reprinted#

    从ASP.NET 1.0 起,相比于ASP中的COM, PipeLine 就是一项重大的改进. ASP.NET 时代的管道模型究竟是怎么样的? 我们能接触到的四个最重要的概念就是HttpApplica ...

  5. webapi框架搭建-日志管理log4net

    前言 本篇讲怎么在前几篇已经创建好的项目里加上日志处理机制,我们采用Log4net技术.跟多的log4net技术的细节请查阅log4net的官网. log4net官网:http://logging.a ...

  6. 大型Vuex应用程序的目录结构

    译者按: 听前端大佬聊聊Vuex大型项目架构的经验 原文: Large-scale Vuex application structures 译者: Fundebug 为了保证可读性,本文采用意译而非直 ...

  7. IIS7注册本机模块

    问题描述:打开mp4文件要映射给mod_h264_streaming.dll(http://h264.code-shop.com/trac/wiki/Mod-H264-Streaming-Intern ...

  8. 没有为扩展名“.html”注册的生成提供程序

    asp.net提示“ 没有为扩展名“.html”注册的生成提供程序... ” 解决办法,修改web.config文件: <compilation debug="true"&g ...

  9. Angularjs 服务注册

    $injector: (When you request a service, the $injector is responsible for finding the correct service ...

随机推荐

  1. hadoop 二次排序的思考

    name sorce jx 10 gj 15 jx 20 gj 30 1. 二次排序 key如何定义 2. grouping 是不是一定要实现,不实现可以吗? 3. 二次排序的本质是什么 4. 如果以 ...

  2. 正则表达式re.sub替换不完整的问题现象及其根本原因

    title: 正则表达式re.sub替换不完整的问题现象及其根本原因 toc: true comment: true date: 2018-08-27 21:48:22 tags: ["Py ...

  3. kaggle首秀之intel癌症预测(续篇)

    之前写了这篇文章.现在把他搬到知乎live上了.书非借不能读也,因此搞了点小费用,如果你觉得贵,加我微信我给你发红包返回给你. 最近的空余时间拿去搞kaggle了, 好久没更新文章了.今天写写kagg ...

  4. MM-实际应用中的难题

    SAP系统实际应用中的十大难题——塞依SAP培训 难题1:采购料维修 如果有物料坏了,需要退回给供应商处维修,此时一般不做退货.因为,第一,供应商不一定会乐意:第二,往来单据也无谓地增多:第三,最重要 ...

  5. mysql 乐观锁实现

    一.为什么需要锁(并发控制)?      在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突.这就是著名的并发性问题.      典型的冲突有:        1.丢失更新:一个事 ...

  6. office 安装

    在 gaobo百度云下载安装包. 自定义安装,并在自定义界面选择安装路径. 破解:

  7. linux安装redis-4.0.2

    一.下载redis cd /usr/local/src wget http://download.redis.io/releases/redis-4.0.2.tar.gz 二.解压 tar -zxvf ...

  8. Swift 模型属性

    1 .  // 定义模型属性时,一般定义为可选的,可以简化代码,不需要写 init 方法    // 如果是基本数据类型,不能设置为可选的,而且要设置初始值 var name: String? pri ...

  9. MySQL优化(二) 优化诀窍

    一.索引的使用 (1)查询要使用索引最重要的条件是查询条件中的字段建立了索引: (2)下列几种情况可能使用到索引: <1> 对于创建的多列索引,只要查询条件使用了最坐边的列,索引一般就会被 ...

  10. SPS Programming Abrites AVDI or GM MDI

    Just information for you to make a wise purchase of GM scan tools for SPS programming: (here, I focu ...