zoukankan      html  css  js  c++  java
  • 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 中。

  • 相关阅读:
    java hashmap 缓存
    android 界面刷新 post send
    android 五种 布局文件
    通过枚举窗口,实现最小化到托盘中程序的窗口显示
    转 String,CString,TCHAR*,char*之间区别和联系
    标准C++ 时间日期函数
    话说程序员的职业生涯
    李开复:创业必备
    职业化的软件工程师
    让高版本ie兼容低版本
  • 原文地址:https://www.cnblogs.com/LeeGof/p/9018969.html
Copyright © 2011-2022 走看看