Weex 原生 Module 調(diào)用解讀

Weex 原生 Module 調(diào)用解讀

@(編程筆記)[Weex]
先放結(jié)論

結(jié)論

總體分為兩段:SDK初始化,和 WeexInstance 實(shí)例。

  1. WeexSDK 初始化

WXJSCoreBridge 被初始化時(shí),調(diào)用registerCallNativeModule方法,向jscontext注入callNativeModule全局block函數(shù)。

  1. WeexInstance 實(shí)例

原生端 WXSDKInstance init 的時(shí)候,生成 instanceId,用 instanceId 去初始化 js 端[[WXSDKManager bridgeMgr] createInstance:self.instanceId template:mainBundleString options:dictionary data:_jsData];

JS 端 WeexInstance init 的時(shí)候注冊(cè)requireModule(moduleName),里面將weexInstance 的 id 傳入。


當(dāng)需要調(diào)用的時(shí)候通過(guò)TaskCenter解析成 (id, module, method, args, options) 形式的參數(shù),發(fā)起原生調(diào)用,原生拿到id,獲取 id 對(duì)應(yīng) vc 。自然也就能拿到 vc 的 navigationController,發(fā)起 push。

Navigator weexInstance 引出的問(wèn)題

最近在開發(fā)時(shí)遇到 Navigator 的問(wèn)題。

首先 Navigator 的 push 方法是這樣的:

- (void)push:(NSDictionary *)param callback:(WXModuleCallback)callback
{
    id<WXNavigationProtocol> navigator = [self navigator];
    UIViewController *container = self.weexInstance.viewController;
    [navigator pushViewControllerWithParam:param completion:^(NSString *code, NSDictionary *responseData) {
        if (callback && code) {
            callback(code);
        }
    } withContainer:container];
}

具體說(shuō)就是:

  1. 獲取注冊(cè)的navigator
  2. 獲取當(dāng)前調(diào)用這個(gè)方法的weex實(shí)例,也就是weexInstance作為vc標(biāo)示
  3. 用獲取到的weexInstance作為基礎(chǔ),調(diào)用它的navigationController去 push

這樣的好處自然是push流程的正確而安全。

畢竟若是沒(méi)有調(diào)用vc的實(shí)例,那么我們往往只能拿取當(dāng)前UIWindow頂端的visibleViewController

public extension UIWindow {

    /// Return the current visible view controller
    public var visibleViewController: UIViewController? {
        return UIWindow.getVisibleViewControllerFrom(self.rootViewController)
    }

    public static func getVisibleViewControllerFrom(_ vc: UIViewController?) -> UIViewController? {
        if let nc = vc as? UINavigationController {
            return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
        } else if let tc = vc as? UITabBarController {
            return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
        } else {
            if let pvc = vc?.presentedViewController {
                return UIWindow.getVisibleViewControllerFrom(pvc)
            } else {
                return vc
            }
        }
    }
}


let currentVC = UIApplication.shared.delegate?.window??.visibleViewController
currentVC?.navigationController?.popViewController(animated: true)

問(wèn)題是 Navigator 這個(gè)模塊的weexInstance是哪來(lái)的?

開始調(diào)試

為了搞清這個(gè)問(wèn)題, 我把 Navigator 的 weexInstance 展開并打斷點(diǎn):

@synthesize weexInstance = _weexInstance;

- (void)setWeexInstance:(WXSDKInstance *)weexInstance {
    _weexInstance = weexInstance;
}

- (id)weexInstance {
    return _weexInstance;
}

然后,我使用如下腳本

<script>
console.log('Before require')
var navigator = weex.requireModule('navigator')
console.log('After require')
export default {
  created () {
    console.log('onCreated')
  },
  methods: {
    onClickBack (event) {
      navigator.pop()
    },
  }
}
</script>

經(jīng)過(guò)測(cè)試,頁(yè)面顯示的時(shí)候這個(gè)var navigator 已經(jīng)執(zhí)行了,但是weexInstance并沒(méi)有被設(shè)置,是點(diǎn)擊的時(shí)候,點(diǎn)擊響應(yīng)onClickBack的pop函數(shù)才設(shè)置的weexInstance。

通過(guò)查看源碼也發(fā)現(xiàn),這個(gè)requireModule是建立了對(duì)模塊的一個(gè)proxy或者是索引。在點(diǎn)擊的時(shí)候這個(gè)var的pop被調(diào)用的時(shí)候weexInstance才被注入。
而中間這一part隱藏內(nèi)容,就是原生接收到方法調(diào)用的時(shí)候注入weexInstance。

When

所以得出來(lái):在onClicknavigator.pop()調(diào)用被傳遞到原生的時(shí)候,才拿到的weexInstance,而非 script 執(zhí)行的時(shí)候。

WXModuleProtocol

事實(shí)上這個(gè) weexInstance 是被聲明在WXModuleProtocol的,不單單是Navigator,每一個(gè)Module都會(huì)有weexInstance這一個(gè)實(shí)例變量。

@protocol WXModuleProtocol <NSObject>
// 代碼有刪減
/**
 *  @abstract the instance bind to this module. It helps you to get many useful properties related to the instance.
 */
@property (nonatomic, weak) WXSDKInstance *weexInstance;

@end

WXJSCoreBridge - registerCallNativeModule:

經(jīng)過(guò)一番調(diào)查,定位到了WXJSCoreBridge- (void)registerCallNativeModule:(WXJSCallNativeModule)callNativeModuleBlock

- (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];
        
        WXLogDebug(@"callNativeModule...%@,%@,%@,%@", instanceIdString, moduleNameString, methodNameString, argsArray);
        
        NSInvocation *invocation = callNativeModuleBlock(instanceIdString, moduleNameString, methodNameString, argsArray, optionsDic);
        JSValue *returnValue = [JSValue wx_valueWithReturnValueFromInvocation:invocation inContext:[JSContext currentContext]];
        [WXTracingManager startTracingWithInstanceId:instanceIdString ref:nil className:nil name:moduleNameString phase:WXTracingInstant functionName:methodNameString options:nil];
        return returnValue;
    };
}

- (void)registerCallNativeModule:(WXJSCallNativeModule)callNativeModuleBlock 在SDK初始化的時(shí)候被調(diào)用,向 jsContext 注冊(cè)了callNativeModule

其中NSString *instanceIdString = [instanceId toString]; 把instanceId獲取到并傳遞給 Module,同樣的方法拿到 moduleName 和 methodName,而這個(gè) instanceId 就是調(diào)用 Navigator 這個(gè) module 的 weexInstance 的 id。

方法調(diào)用則是通過(guò)NSInvocation。

JSFramework

原生這邊已經(jīng)基本搞定,那么接下來(lái)就是看 JSFramework 這邊的邏輯。

TaskCenter.js

export class TaskCenter {
  callModule (module, method, args, options) {
    return this.moduleHandler(this.instanceId, module, method, args, options)
  }
}
export function init () {
  const proto = TaskCenter.prototype
  proto.moduleHandler = global.callNativeModule ||
    ((id, module, method, args) =>
      fallback(id, [{ module, method, args }]))
}

注入callNativeModuleTaskCenter.prototype,然后再把 callModule 指向它

WeexInstance.js

function setId (weex, id) {
  Object.defineProperty(weex, '[[CurrentInstanceId]]', { value: id })
}

function getId (weex) {
  return weex['[[CurrentInstanceId]]']
}

function moduleGetter (id, module, method) {
  const taskCenter = getTaskCenter(id)
  if (!taskCenter || typeof taskCenter.send !== 'function') {
    console.error(`[JS Framework] Failed to find taskCenter (${id}).`)
    return null
  }
  return (...args) => taskCenter.send('module', { module, method }, args)
}

function moduleSetter (id, module, method, fn) {
  const taskCenter = getTaskCenter(id)
  if (!taskCenter || typeof taskCenter.send !== 'function') {
    console.error(`[JS Framework] Failed to find taskCenter (${id}).`)
    return null
  }
  if (typeof fn !== 'function') {
    console.error(`[JS Framework] ${module}.${method} must be assigned as a function.`)
    return null
  }
  return fn => taskCenter.send('module', { module, method }, [fn])
}

在文件內(nèi)注冊(cè) moduleSettermoduleGetter,

export default class WeexInstance {
  constructor (id, config) {
    setId(this, String(id))
    this.config = config || {}
    this.document = new Document(id, this.config.bundleUrl)
    this.requireModule = this.requireModule.bind(this)
    this.isRegisteredModule = isRegisteredModule
    this.isRegisteredComponent = isRegisteredComponent
  }
  
  requireModule (moduleName) {
    const id = getId(this)
    if (!(id && this.document && this.document.taskCenter)) {
      console.error(`[JS Framework] Failed to requireModule("${moduleName}"), `
        + `instance (${id}) doesn't exist anymore.`)
      return
    }

    // warn for unknown module
    if (!isRegisteredModule(moduleName)) {
      console.warn(`[JS Framework] using unregistered weex module "${moduleName}"`)
      return
    }

    // create new module proxy
    const proxyName = `${moduleName}#${id}`
    if (!moduleProxies[proxyName]) {
      // create registered module apis
      const moduleDefine = getModuleDescription(moduleName)
      const moduleApis = {}
      for (const methodName in moduleDefine) {
        Object.defineProperty(moduleApis, methodName, {
          enumerable: true,
          configurable: true,
          get: () => moduleGetter(id, moduleName, methodName),
          set: fn => moduleSetter(id, moduleName, methodName, fn)
        })
      }

      moduleProxies[proxyName] = moduleApis
    }

    return moduleProxies[proxyName]
  }
}

獲取當(dāng)前實(shí)例的id,傳入方法調(diào)用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容