Weex 原生 Module 調(diào)用解讀
@(編程筆記)[Weex]
先放結(jié)論
結(jié)論
總體分為兩段:SDK初始化,和 WeexInstance 實(shí)例。
- WeexSDK 初始化
在 WXJSCoreBridge 被初始化時(shí),調(diào)用registerCallNativeModule方法,向jscontext注入callNativeModule全局block函數(shù)。
- 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ō)就是:
- 獲取注冊(cè)的
navigator - 獲取當(dāng)前調(diào)用這個(gè)方法的weex實(shí)例,也就是
weexInstance作為vc標(biāo)示 - 用獲取到的
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):在onClick的navigator.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 }]))
}
注入callNativeModule 到 TaskCenter.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è) moduleSetter 和 moduleGetter,
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)用。