注册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. luigi 学习

    1.mac 上安装luigi pip install luigi pip install boto3 (luigi依赖 boto3) 2.基本概念 class Streams(luigi.Task): ...

  2. (5)修改Linux的基本配置

    **IP地址配置,最简单的方法:在命令行运行setup,按照提示修改即可. 1.修改主机名 vi /etc/sysconfig/network NETWORKING=yes HOSTNAME=serv ...

  3. leetcode148

    class Solution { public: ListNode* sortList(ListNode* head) { multimap<int,ListNode*> mul; whi ...

  4. NetBeans 代码折叠

    代码折叠 // <editor-fold>   Your code goes here...// </editor-fold> 添加描述 // <editor-fold ...

  5. Python3 timeit的用法

    Python3中的timeit模块可以用来测试小段代码的运行时间 其中主要通过两个函数来实现:timeit和repeat,代码如下: def timeit(stmt="pass", ...

  6. python入门学习0

    Python 是什么类型的语言 Python是脚本语言 Python下载地址:https://www.python.org/downloads/ Python版本:Python 3.4.2 - 64b ...

  7. Linux下安装PHP环境并配置Nginx支持php-fpm模块

    修改php配置 vi /etc/php.ini 打开php配置文件/etc/php.ini找到cgi.fix_pathinfo配置项,这一项默认被注释并且值为1,根据官方文档的说明,这里为了当文件不存 ...

  8. python--第十二天总结(Python操作 RabbitMQ、Redis、Memcache、SQLAlchemy)

    Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度 ...

  9. Why is it called “armature” instead of “skeleton”? or perhaps “rig”?

    Great question, I’ve always assumed armature/skeleton to be the same thing, here’s a quote from an a ...

  10. [leetcode]5. Longest Palindromic Substring最长回文子串

    Given a string s, find the longest palindromic substring in s. You may assume that the maximum lengt ...