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 中。

 

posted @ 2018-05-10 16:18  LeeGof  阅读(...)  评论(...编辑  收藏