WeexSDK之注册Modules
注册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 中。