【原創(chuàng)】Weex 源碼探析 —— Module 的注冊和調(diào)用

整個 Weex 的工作原理大致可以用一張圖來表述:



這次分享的重點在 Native Module 如何注冊并切可以被 JS 調(diào)用的倡鲸。

初始化

首先我們通過 [WXSDKEngine initSDKEnvironment] 初始化 SDK 環(huán)境

+ (void)initSDKEnvironment
{
    ……   
    // 獲取 main.js 文件路徑
    NSString *filePath = [[NSBundle bundleForClass:self] pathForResource:@"main" ofType:@"js"];
    // 讀出 main.js 文件內(nèi)容
    NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    [WXSDKEngine initSDKEnvironment:script];
    ……
}
      
+ (void)initSDKEnvironment:(NSString *)script
{
    ……
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
     // 注冊默認內(nèi)容 這個稍后會說
        [self registerDefaults];
        // BridgeManager 執(zhí)行 JS
        [[WXSDKManager bridgeMgr] executeJsFramework:script];
    });
}

讓我們忽略掉一些邊界處理砾层、監(jiān)控缩滨、日志的代碼,只提取其中的核心代碼(以下同)泊业。Weex 將 Bundle 中的 main.js 加載油讯,交給 Bridge Manager 執(zhí)行。我們可以查看 main.js 是一個壓縮后的 js 文件田弥,其實它就是 Weex JS 的 Framework涛酗,我們調(diào)用的 Weex JS API 就是在這里定義的。那么剛剛執(zhí)行它的 Bridge Manager 又是做什么的呢偷厦?

Bridge 模塊

Bridge 模塊就是負責 Native 與 JS 通訊的煤杀,有下面這些類組成。


WXBridgeManager

Bridge 模塊還有一個非常重要的類沪哺,是在 Manager 路徑下的 WXBridgeManager沈自,它是整個模塊的對外API,單例辜妓。它維護了一個線程來 Loop 處理事件枯途,還關(guān)聯(lián)了 Bridge Context,比如 register module籍滴、execute JS 等 JS 的交互操作實際都是在 Bridge Context 中執(zhí)行的酪夷。下面來介紹這個 Bridge Context。

WXBridgeContext

Bridge Context 的主要功能如上文說孽惰,是給 Bridge Manager 提供與 JS 交互的能力晚岭,所以除了注冊 Module、Service 等之外勋功,就是調(diào)用 JS 方法和服務(wù)坦报,所以它維護了 sendQueue、serviceQueue 兩個隊列來分別管理方法和服務(wù)的調(diào)用消息狂鞋,另外還有一個 methodQueue片择,是來存儲在 Framework 加載之前就被發(fā)送的消息,在 Framework 加載完成之后再執(zhí)行骚揍。它還維持了一個 weex instance id stack字管,在 WXSDKManager 中有一個 id -> WXInstance 的哈希表,可以通過 id 獲取到對應(yīng)的 WXInstance信不。

WXBridgeProtocol

WXBridgeContext 真正的 JS Native 交互能力依賴于它關(guān)聯(lián)的 id<WXBridgeProtocol>嘲叔,這個 Protocol 的聲明如下:

@protocol WXBridgeProtocol <NSObject>
@property (nonatomic, readonly) JSValue* exception;
/**
  * Executes the js framework code in javascript engine
  * You can do some setup in this method
  */
- (void)executeJSFramework:(NSString *)frameworkScript;
/**
  * Executes the js code in javascript engine
  * You can do some setup in this method
  */
- (void)executeJavascript:(NSString *)script;
/**
 * Executes global js method with specific arguments
 */
- (JSValue *)callJSMethod:(NSString *)method args:(NSArray*)args;
/**
 * Register callback when call native tasks occur
 */
- (void)registerCallNative:(WXJSCallNative)callNative;
/**
 * Reset js engine environment, called when any environment variable is changed.
 */
- (void)resetEnvironment;
@optional
/**
 * Called when garbage collection is wanted by sdk.
 */
- (void)garbageCollect;
/**
 * Register callback when addElement tasks occur
 */
- (void)registerCallAddElement:(WXJSCallAddElement)callAddElement;
/**
 * Register callback for global js function `callNativeModule`
 */
- (void)registerCallNativeModule:(WXJSCallNativeModule)callNativeModuleBlock;
/**
 * Register callback for global js function `callNativeComponent`
 */
- (void)registerCallNativeComponent:(WXJSCallNativeComponent)callNativeComponentBlock;
      
@end

很顯然,它提供了 Native 和 JS 交互的最基本功能抽活,包括執(zhí)行 JS 代碼硫戈,調(diào)用方法,注冊 Native 回調(diào)等酌壕。這個協(xié)議有兩個實現(xiàn)類:WXJSCoreBridge 和 WXDebugLoggerBridge掏愁。其中 WXJSCoreBridge 是使用蘋果的 JavaScriptCore 這個引擎來實現(xiàn)的。而 WXDebugLoggerBridge 則是用于調(diào)試卵牍,將 JS Bridge 對接到一個遠程的 websocket 服務(wù)器果港,而不是本地的 JS 引擎。本地準備一個網(wǎng)頁糊昙,運行了完整的 JS 引擎的代碼辛掠,并且也可以鏈接到一個遠程的 websocket 服務(wù)器,這樣客戶端的 native 層和本地網(wǎng)頁里面的 JS 引擎就串聯(lián)起來了释牺,原本通過 JS Bridge 的雙向通信內(nèi)容就可以被 websocket 連接記錄下來萝衩,JS 引擎中里的所有代碼都可以通過本地瀏覽器的開發(fā)者工具進行 debug 和 console 控制。

其他

WXBridgeMethod 是其他各種 Method 的基類没咙,他們是封裝的方法模型猩谊,包括調(diào)用對象的 Ref、方法名祭刚、參數(shù)數(shù)組牌捷、weex instance 等,可以通過獲取 NSInvocation 調(diào)用相應(yīng)的 Native 方法涡驮。
JSValue Category 是個工具暗甥,可以將 NSInvocation 的返回值轉(zhuǎn)換成 JS 對應(yīng)的類型。
WXPolyfillSet 是一個 JSExport捉捅,封裝了一個 NSMutableSet撤防,供 JS 調(diào)用。

總結(jié)

整個 Weex Bridge 模塊的主要類圖如下所示棒口。


Native Module 的注冊和調(diào)用

以 Native Module 為例寄月,學習一下 Weex 是如何通過 JS 調(diào)用 Native 代碼的。

注冊

我們通過[WXSDKEngin registerModule:withClass:]方法來在 Weex 中注冊 Module无牵。

+ (void)registerModule:(NSString *)name withClass:(Class)clazz
{
    NSString *moduleName = [WXModuleFactory registerModule:name withClass:clazz];
    NSDictionary *dict = [WXModuleFactory moduleMethodMapsWithName:moduleName];
     
    [[WXSDKManager bridgeMgr] registerModules:dict];
}

代碼中先在 ModuleFactory 中注冊 Module 并獲取 moduleName剥懒,再從在 ModuleFactory 中獲取到 moduleMethod 的哈希表,并將這個哈希表注冊在 Bridge Manager 中合敦。
下面來具體看看每一步注冊具體是如何做的初橘。

WXModuleFactory 中的注冊

- (NSString *)_registerModule:(NSString *)name withClass:(Class)clazz
{        
    [_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;
}

在 ModuleFactory 的 moduleMap 中,加入了一個 WXModuleConfig充岛,WXModuleConfig 記錄了 name 和 class保檐,調(diào)用 [WXModuleConfig registerMethods] 注冊了方法。

- (void)registerMethods
{
    Class currentClass = NSClassFromString(_clazz);
     
    if (!currentClass) {
        WXLogWarning(@"The module class [%@] doesn't exit崔梗!", _clazz);
        return;
    }
     
    while (currentClass != [NSObject class]) {
        unsigned int methodCount = 0;
        Method *methodList = class_copyMethodList(object_getClass(currentClass), &methodCount);
        for (unsigned int i = 0; i < methodCount; i++) {
            NSString *selStr = [NSString stringWithCString:sel_getName(method_getName(methodList[i])) encoding:NSUTF8StringEncoding];
            BOOL isSyncMethod = NO;
            if ([selStr hasPrefix:@"wx_export_method_sync_"]) {
                isSyncMethod = YES;
            } else if ([selStr hasPrefix:@"wx_export_method_"]) {
                isSyncMethod = NO;
            } else {
                continue;
            }
             
            NSString *name = nil, *method = nil;
            SEL selector = NSSelectorFromString(selStr);
            if ([currentClass respondsToSelector:selector]) {
                method = ((NSString* (*)(id, SEL))[currentClass methodForSelector:selector])(currentClass, selector);
            }
             
            if (method.length <= 0) {
                WXLogWarning(@"The module class [%@] doesn't has any method夜只!", _clazz);
                continue;
            }
             
            NSRange range = [method rangeOfString:@":"];
            if (range.location != NSNotFound) {
                name = [method substringToIndex:range.location];
            } else {
                name = method;
            }
             
            NSMutableDictionary *methods = isSyncMethod ? _syncMethods : _asyncMethods;
            [methods setObject:method forKey:name];
        }
         
        free(methodList);
        currentClass = class_getSuperclass(currentClass);
    }
     
}

[WXModuleConfig registerMethods]方法非常長,簡單來說就是:先通過 while 循環(huán)遍歷自己和父類的方法列表中的每一個方法蒜魄,如果方法名以 wx_export_method_sync_ 或 wx_export_method_ 開頭扔亥,則通過 OC runtime 獲取到方法所對應(yīng)的函數(shù)指針场躯,并調(diào)用這個函數(shù),得到 method 字符串旅挤,最后將 method 字符串放入同步或異步的方法表里踢关。
這里面有幾個疑問:wx_export_method_sync、wx_export_method 開頭的方法是在哪里聲明的粘茄?調(diào)用它得到的 method 字符串又是什么签舞?
在 Weex Module 中,我們需要用 WX_EXPORT_METHOD柒瓣、WX_EXPORT_METHOD_SYNC 這兩個宏來聲名 JS 異步和同步方法儒搭,問題應(yīng)該就是在這里,所以看看這兩個宏的定義芙贫。

/*
 * Concatenate preprocessor tokens a and b without expanding macro definitions
 * (however, if invoked from a macro, macro arguments are expanded).
 */
#define WX_CONCAT(a, b)   a ## b
/*
 * Concatenate preprocessor tokens a and b after macro-expanding them.
 */
#define WX_CONCAT_WRAPPER(a, b)    WX_CONCAT(a, b)
  
#define WX_EXPORT_METHOD_INTERNAL(method, token) \
+ (NSString *)WX_CONCAT_WRAPPER(token, __LINE__) { \
    return NSStringFromSelector(method); \
}
  
 
/**
 *  @abstract export public method
 */
#define WX_EXPORT_METHOD(method) WX_EXPORT_METHOD_INTERNAL(method,wx_export_method_)
/**
 *  @abstract export public method, support sync return value
 *  @warning the method can only be called on js thread
 */
#define WX_EXPORT_METHOD_SYNC(method) WX_EXPORT_METHOD_INTERNAL(method,wx_export_method_sync_)

這兩個宏的作用就是定義了一個類方法搂鲫,以 wx_export_method、wx_export_method_sync_ 加上所在代碼的行數(shù)組成方法名磺平,返回 selector 的字符串默穴,上面的疑問得以解決。
回到剛才的步驟褪秀, 從 ModuleFactory 中獲取到 moduleMethod 的哈希表蓄诽,它是將 config 中的同步和異步方法都裝入一個數(shù)組中,以 moduleName 為 key媒吗,數(shù)組為 value 生成一個單項哈希表仑氛。
至此,在 WXModuleFactory 中的注冊已經(jīng)基本完成了:moduleMap 哈希表中用 moduleName 作為 key闸英,moduleConfig 作為 value锯岖;moduleConfig 中記錄著 className 和 moduleName,同時還有兩個哈希表甫何,存儲同步和異步方法出吹。

WXBridgeManager 中的注冊

Bridge Manager 是調(diào)用 Bridge Context 執(zhí)行的注冊。

- (void)registerModules:(NSDictionary *)modules
{
    WXAssertBridgeThread();
     
    if(!modules) return;
     
    [self callJSMethod:@"registerModules" args:@[modules]];
}
  
- (void)callJSMethod:(NSString *)method args:(NSArray *)args
{
    if (self.frameworkLoadFinished) {
        [self.jsBridge callJSMethod:method args:args];
    } else {
        [_methodQueue addObject:@{@"method":method, @"args":args}];
    }
}

它是調(diào)用了 JSBridge 的 callJSMethod 方法辙喂,method 為 "registerModules"捶牢,args 參數(shù)是這個 Module 提供給 JS 調(diào)用的所有的同步、異步方法列表巍耗。
之前介紹了 Bridge 模塊下的 JSBridgeProtocol 的兩個實現(xiàn)類秋麸,那么以 WXJSCoreBridge 為例,callJSMethod 的方法實現(xiàn)如下:

- (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args
{
    return [[_jsContext globalObject] invokeMethod:method withArguments:args];
}

直接調(diào)用了 JS 全局對象的registerModules方法炬太。那么我們就看看 JS 中的 registerModules 方法是如何定義的灸蟆。

/**
 * Register the name and methods of each module.
 * @param  {object} modules a object of modules
 */
export function registerModules (modules) {
  /* istanbul ignore else */
  if (typeof modules === 'object') {
    initModules(modules)
  }
}
 
 
/**
 * init modules for an app instance
 * the second param determines whether to replace an existed method
 */
export function initModules (modules, ifReplace) {
  for (const moduleName in modules) {
    // init `modules[moduleName][]`
    let methods = nativeModules[moduleName]
    if (!methods) {
      methods = {}
      nativeModules[moduleName] = methods
    }
 
    // push each non-existed new method
    modules[moduleName].forEach(function (method) {
      if (typeof method === 'string') {
        method = {
          name: method
        }
      }
 
      if (!methods[method.name] || ifReplace) {
        methods[method.name] = method
      }
    })
  }
}

簡而言之就是將 name 和方法哈希中的方法都寫入了 nativeModules 這個對象中。

調(diào)用

我們在 JS 中使用weex.requireModule('xxx')來獲取 module亲族,再調(diào)用它的方法炒考。那么 weex 這個全局變量是哪里定義的呢可缚?它是怎么能夠被獲取到的?requireModule()又做了什么斋枢?

weex 對象

每個 weex 頁面都是一個 WXSDKInstance帘靡,weex instance 在加載到 JS bundle 的代碼之后,會調(diào)用renderWithMainBundleString:方法杏慰。

- (void)_renderWithMainBundleString:(NSString *)mainBundleString
{                
    NSMutableDictionary *dictionary = [_options mutableCopy];        
    WXPerformBlockOnMainThread(^{
        _rootView = [[WXRootView alloc] initWithFrame:self.frame];
        _rootView.instance = self;
        if(self.onCreate) {
            self.onCreate(_rootView);
        }
    });
     
    // ensure default modules/components/handlers are ready before create instance
    [WXSDKEngine registerDefaults];
     
    [[WXSDKManager bridgeMgr] createInstance:self.instanceId template:mainBundleString options:dictionary data:_jsData];
}

先在主線程中創(chuàng)建了 WXRootView空另,并調(diào)用了 onCreate 回調(diào)黄鳍,最后調(diào)用了[WXBridgeManager createInstance:template:options:data]旗笔,將 mainBundleString 傳給了 BridgeManager朽肥。
在 BridgeManager 中切換了執(zhí)行線程手蝎,將任務(wù)交給 Bridge Context 來執(zhí)行活喊。

- (void)createInstance:(NSString *)instance
              template:(NSString *)temp
               options:(NSDictionary *)options
                  data:(id)data
{        
    ……
    [self callJSMethod:@"createInstance" args:args];
}

最后 Bridge Context 調(diào)用了createInstance()這個 JS 方法搜囱,將 instanceId以舒、mainBundleString 等參數(shù)都傳了過去霎肯。
我們看看 JS 中createInstance()這個方法的實現(xiàn)擎颖。


/*
 * @param  {string} id
 * @param  {string} code
 * @param  {object} options
 *         option `HAS_LOG` enable print log
 * @param  {object} data
 * @param  {object} info { created, ... services }
 */
export function createInstance (id, code, options, data, info) {
  const { services } = info || {}
  resetTarget()
  let instance = instanceMap[id]
  /* istanbul ignore else */
  options = options || {}
  let result
  /* istanbul ignore else */
  if (!instance) {
    instance = new App(id, options)
    instanceMap[id] = instance
    result = initApp(instance, code, data, services)
  }
  else {
    result = new Error(`invalid instance id "${id}"`)
  }
  return result
}
  
export function init (app, code, data, services) {
  //……
  //……
  /* istanbul ignore next */
  const bundleRequireModule = name => app.requireModule(removeWeexPrefix(name))
 
 
  const weexGlobalObject = {
    config: app.options,
    define: bundleDefine,
    bootstrap: bundleBootstrap,
    requireModule: bundleRequireModule,
    document: bundleDocument,
    Vm: bundleVm
  }
 
  Object.freeze(weexGlobalObject)
 
  // prepare code
  let functionBody
  /* istanbul ignore if */
  if (typeof code === 'function') {
    // `function () {...}` -> `{...}`
    // not very strict
    functionBody = code.toString().substr(12)
  }
  /* istanbul ignore next */
  else if (code) {
    functionBody = code.toString()
  }
  // wrap IFFE and use strict mode
  functionBody = `(function(global){\n\n"use strict";\n\n ${functionBody} \n\n})(Object.create(this))`
   
  // ……
  
// run code and get result
  const globalObjects = Object.assign({
    define: bundleDefine,
    require: bundleRequire,
    bootstrap: bundleBootstrap,
    register: bundleRegister,
    render: bundleRender,
    __weex_define__: bundleDefine, // alias for define
    __weex_bootstrap__: bundleBootstrap, // alias for bootstrap
    __weex_document__: bundleDocument,
    __weex_require__: bundleRequireModule,
    __weex_viewmodel__: bundleVm,
    weex: weexGlobalObject
  }, timerAPIs, services)
  callFunction(globalObjects, functionBody)
 
  return result
}
  
/**
 * Call a new function body with some global objects.
 * @param  {object} globalObjects
 * @param  {string} code
 * @return {any}
 */
function callFunction (globalObjects, body) {
  const globalKeys = []
  const globalValues = []
  for (const key in globalObjects) {
    globalKeys.push(key)
    globalValues.push(globalObjects[key])
  }
  globalKeys.push(body)
 
  const result = new Function(...globalKeys)
  return result(...globalValues)
}

代碼非常多,所以只截取了我們今天要關(guān)注的部分观游。
首先在createInstance()函數(shù)中搂捧,創(chuàng)建了 App 對象,并將它存儲在 instanceMap 中懂缕。
init()函數(shù)中允跑,先定義了 weexGlobalObject,這也就是我們 JS bundle 代碼中要使用的 weex 對象搪柑,其中也聲名了我們需要的requireModule()方法聋丝。而后又將傳入的 mainBundleString 封裝在了 IIFE 中。然后又定義了 globalObjects 對象工碾,里面除了包含了 weex弱睦、require、bootstrap渊额、render 等多個成員外况木,也包含了 timerAPIs 和 services 中的成員。
最后在callFunction()函數(shù)中旬迹,將 globalObjects 中的所有成員都作為參數(shù)焦读,調(diào)用了之前封裝的 IIFE。至此我們的 JS bundle 代碼中直接使用的 weex 實例就被這樣傳遞過來了舱权。

requireModule()

后面的問題就簡單多了矗晃,在 App 中查看requireModule()的實現(xiàn)。

/**
 * @deprecated
 */
App.prototype.requireModule = function (name) {
  return requireModule(this, name)
}
 
 
  
/**
 * get a module of methods for an app instance
 */
export function requireModule (app, name) {
  const methods = nativeModules[name]
  const target = {}
  for (const methodName in methods) {
    Object.defineProperty(target, methodName, {
      configurable: true,
      enumerable: true,
      get: function moduleGetter () {
        return (...args) => app.callTasks({
          module: name,
          method: methodName,
          args: args
        })
      },
      set: function moduleSetter (value) {
        if (typeof value === 'function') {
          return app.callTasks({
            module: name,
            method: methodName,
            args: [value]
          })
        }
      }
    })
  }
  return target
}

從 nativeModules 中取到之前注冊時候存入的 methods 遍歷宴倍,將方法的相關(guān)信息設(shè)為 target 的成員张症,最后將 target 返回仓技。調(diào)用的話是直接將 module name、方法名和參數(shù)封裝俗他,傳遞給app.callTasks()脖捻。

調(diào)用 native

先看看app.callTasks()的實現(xiàn)。

/**
 * @deprecated
 */
App.prototype.callTasks = function (tasks) {
  return callTasks(this, tasks)
}
 
 
/**
 * Call all tasks from an app to renderer (native).
 * @param  {object} app
 * @param  {array}  tasks
 */
export function callTasks (app, tasks) {
  let result
 
  /* istanbul ignore next */
  if (typof(tasks) !== 'array') {
    tasks = [tasks]
  }
 
  tasks.forEach(task => {
    result = app.doc.taskCenter.send(
      'module',
      {
        module: task.module,
        method: task.method
      },
      task.args
    )
  })
 
  return result
}

對 taskCenter 發(fā)送消息兆衅,將 module地沮、method 和 args 都傳遞過去。

export class TaskCenter {
  ……
  send (type, options, args) {
    const { action, component, ref, module, method } = options
 
    args = args.map(arg => this.normalize(arg))
 
    switch (type) {
      case 'dom':
        return this[action](this.instanceId, args)
      case 'component':
        return this.componentHandler(this.instanceId, ref, method, args, { component })
      default:
        return this.moduleHandler(this.instanceId, module, method, args, {})
    }
  }
  ……
}
  
export function init () {
  const DOM_METHODS = {
    createFinish: global.callCreateFinish,
    updateFinish: global.callUpdateFinish,
    refreshFinish: global.callRefreshFinish,
 
    createBody: global.callCreateBody,
 
    addElement: global.callAddElement,
    removeElement: global.callRemoveElement,
    moveElement: global.callMoveElement,
    updateAttrs: global.callUpdateAttrs,
    updateStyle: global.callUpdateStyle,
 
    addEvent: global.callAddEvent,
    removeEvent: global.callRemoveEvent
  }
  const proto = TaskCenter.prototype
 
  for (const name in DOM_METHODS) {
    const method = DOM_METHODS[name]
    proto[name] = method ?
      (id, args) => method(id, ...args) :
      (id, args) => fallback(id, [{ module: 'dom', method: name, args }], '-1')
  }
 
  proto.componentHandler = global.callNativeComponent ||
    ((id, ref, method, args, options) =>
      fallback(id, [{ component: options.component, ref, method, args }]))
 
  proto.moduleHandler = global.callNativeModule ||
    ((id, module, method, args) =>
      fallback(id, [{ module, method, args }]))
}

taskCenter 這個類是 JS 到 native 的橋梁羡亩,其中的 send 方法摩疑,會根據(jù) type 對發(fā)送來的消息做不同的處理,現(xiàn)在的參數(shù)是 'module' 則調(diào)用moduleHandler()畏铆。在 init() 函數(shù)中定義了moduleHandler = global.callNativeModule雷袋,感覺就快找到目標了。
最后終于到callNativeModule函數(shù)定義在了這里:

@implementation WXJSCoreBridge
  
……
  
- (void)registerCallNativeModule:(WXJSCallNativeModule)callNativeModuleBlock
{
    _jsContext[@"callNativeModule"] = ^JSValue *(JSValue *instanceId, JSValue *moduleName, JSValue *methodName, JSValue *args, JSValue *options) {
        NSString *instanceIdString = [instanceId toString];
        NSString *moduleNameString = [moduleName toString];
        NSString *methodNameString = [methodName toString];
        NSArray *argsArray = [args toArray];
        NSDictionary *optionsDic = [options toDictionary];
         
        NSInvocation *invocation = callNativeModuleBlock(instanceIdString, moduleNameString, methodNameString, argsArray, optionsDic);
        JSValue *returnValue = [JSValue wx_valueWithReturnValueFromInvocation:invocation inContext:[JSContext currentContext]];
        return returnValue;
    };
}
  
……
  
@end
  
@implementation WXBridgeContext
……
- (void)registerGlobalFunctions
{
    ……
    [_jsBridge registerCallNativeModule:^NSInvocation*(NSString *instanceId, NSString *moduleName, NSString *methodName, NSArray *arguments, NSDictionary *options) {
         
        WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
         
        WXModuleMethod *method = [[WXModuleMethod alloc] initWithModuleName:moduleName methodName:methodName arguments:arguments instance:instance];
        return [method invoke];
    }];
    ……
}
……
@end

在 WXJSCoreBridge 中定義了注冊調(diào)用原生模塊的方法辞居,在 WXBridgeContext 中的注冊全局函數(shù)中楷怒,對這個函數(shù)進行了注冊。被調(diào)用的處理很簡單瓦灶,根據(jù) moduleName鸠删、methodName 和 arguments 創(chuàng)建出 WXModuleMethod 并進行調(diào)用。
最后看看 WXModuleMethod 中 invoke 的具體實現(xiàn)贼陶。

- (NSInvocation *)invoke
{
    Class moduleClass =  [WXModuleFactory classWithModuleName:_moduleName];
             
    id<WXModuleProtocol> moduleInstance = [self.instance moduleForClass: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];
        }
        return nil;
    }
     
    NSInvocation *invocation = [self invocationWithTarget:moduleInstance selector:selector];
     
    if (isSync) {
        [invocation invoke];
        return invocation;
    } else {
        [self _dispatchInvocation:invocation moduleInstance:moduleInstance];
        return nil;
    }
}

先從 WXModuleFactory 中獲取到 moduleClass刃泡,再在 weex instance 中獲取到 moduleInstance,最后得到 invocation 對象并根據(jù)同步異步來做不同的調(diào)用每界,整個 module 的注冊和調(diào)用流程就總算完成了捅僵。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市眨层,隨后出現(xiàn)的幾起案子庙楚,更是在濱河造成了極大的恐慌,老刑警劉巖趴樱,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馒闷,死亡現(xiàn)場離奇詭異,居然都是意外死亡叁征,警方通過查閱死者的電腦和手機纳账,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捺疼,“玉大人疏虫,你說我怎么就攤上這事。” “怎么了卧秘?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵呢袱,是天一觀的道長。 經(jīng)常有香客問我翅敌,道長羞福,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任蚯涮,我火速辦了婚禮治专,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遭顶。我一直安慰自己张峰,他們只是感情好,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布液肌。 她就那樣靜靜地躺著挟炬,像睡著了一般鸥滨。 火紅的嫁衣襯著肌膚如雪嗦哆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天婿滓,我揣著相機與錄音老速,去河邊找鬼。 笑死凸主,一個胖子當著我的面吹牛橘券,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播卿吐,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼旁舰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嗡官?” 一聲冷哼從身側(cè)響起箭窜,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎衍腥,沒想到半個月后磺樱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡婆咸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年竹捉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尚骄。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡块差,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情憨闰,我是刑警寧澤询兴,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站起趾,受9級特大地震影響诗舰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜训裆,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一眶根、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧边琉,春花似錦属百、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至定欧,卻和暖如春渔呵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背砍鸠。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工扩氢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人爷辱。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓录豺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親饭弓。 傳聞我的和親對象是個殘疾皇子双饥,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容