摘要:概述提供一個插件式的與交互的框架通過實現(xiàn)插件式擴展接口以供調(diào)用前往查看主要的類大致畫了一下類圖結(jié)合上圖先介紹一下這里幾個類的方法持有一個以截獲的頁面加載的回調(diào)以觸發(fā)注入和環(huán)境初始化的操作委托的代理持有一個真正的并持有一個這樣將與綁定在一
概述
MXBridge,提供一個插件式的JavaScript與Objective-C交互的框架,通過JavaScriptCore實現(xiàn),插件式擴展Obejctive-C接口以供JavaScript調(diào)用.前往Github查看
主要的類大致畫了一下類圖:
結(jié)合上圖,先介紹一下這里幾個類的方法:
UIWebView(MXBridge) : category,持有一個MXWebViewDelegateProxy以截獲UIWebView的頁面加載的回調(diào),以觸發(fā)JS注入和bridge環(huán)境初始化的操作.
MXWebviewDelegateProxy : 委托的代理.持有一個真正的UIWebViewDelegate,并持有一個MXWebViewBridge,這樣將bridge與UIWebView綁定在一起,一個UIWebView中只有一個bridge,并跟隨UIWebView的釋放一起釋放代理和bridge.
MXWebViewBridge : 與JS交互,主要通過這個橋來實現(xiàn). 持有JSContext,也就是當前WebView的JS運行環(huán)境.通過JSExport暴露三個接口供JS直接調(diào)用,同時持有一個從js中獲取的jsBridge對象,即對應(yīng)了JS代碼中的 JSBridgeForOC以供異步回調(diào)時調(diào)用JS代碼. 除了一個setupJSContext的初始化Webview的JS環(huán)境的方法外,還有一個cleanJSContext,在UIWebView釋放時,釋放持有的JS對象指針,以使對象正常釋放.
MXWebViewConntext : 一個單例的全局上下文,放置一些全局的系統(tǒng)信息,以及加載mxbridge.js的代碼以字符串的形式放在內(nèi)存中. 還持有一個插件列表,插件列表的信息放在應(yīng)用中的 plugins.plist文件中,以鍵值對形式儲存插件名和插件對應(yīng)的OC類名.還有一個setUp方法,用于初始化MXBridge的功能,調(diào)用這個方法后,會通過Method Swizzling來為應(yīng)用中所有的UIWebView賦予該功能.
MXWebViewPlugin : 插件,所有OC對JS所提供的方法,都是基于插件的形式,即用戶實現(xiàn)一個插件,然后JS代碼就可以根據(jù)插件名和插件方法名來調(diào)用這個插件的功能.
MXMethodInvocaton : 方法調(diào)用,JS對OC的一次方法調(diào)用中,將參數(shù)以及調(diào)用信息記錄在這個Model中.
實現(xiàn)原理結(jié)合上圖,來介紹一下MXBridge的實現(xiàn)原理,在介紹實現(xiàn)原理之前,建議先去學(xué)習(xí)一下JavaScriptCore的使用方法,MXBridge是基于JavaScriptCore實現(xiàn)的,所以只支持iOS7以上的設(shè)備.
通過Method swizzling來替換了UIWebView的三個方法的實現(xiàn):
-(instancetype)mx_initWithFrame:(CGRect)frame { [self mx_initWithFrame:frame]; if (self) { [self mx_setup]; } return self; } -(nullable instancetype)mx_initWithCoder:(NSCoder *)aDecoder { [self mx_initWithCoder:aDecoder]; if (self) { [self mx_setup]; } return self; } -(void)mx_setDelegate:(id)delegate { // 設(shè)置上真正的代理。 if ([self.delegate isKindOfClass:[MXWebviewDelegateProxy class]]) { ((MXWebviewDelegateProxy *)self.delegate).realDelegate = delegate; }else { [self mx_setDelegate:delegate]; } }
在初始化UIWebView的時候,就會為webview添加一個 MXWebviewDelegateProxy對象作為webviewDelegate,而在使用者使用 setDelegate方法時,將要設(shè)置的delegate賦值給MXWebviewDelegateProxy對象的realDelegate屬性,以讓這個設(shè)置的delegate能夠正常運行.
method swizzling的執(zhí)行是放在MXwebViewContext的setUp方法中的,這個方法作為在整個應(yīng)用中初始化MXBridge環(huán)境,初始化后才能在應(yīng)用里的UIWebView中進行JavaScript和Objective-C之間的交互.
而設(shè)置代理的主要目的,是為了給UIWebView當前界面的JSContext注入我們的MXBridge.js,以獲取交互功能. 在JavaScriptCore中JS代碼都是執(zhí)行在JSContext這個運行環(huán)境中的,JSContext表示JS代碼在OC中的運行環(huán)境,我們可以通過這個環(huán)境以執(zhí)行JS代碼,或者讓JS直接調(diào)用OC方法,具體關(guān)于JavaScriptCore的一些簡介,可以看一下這篇簡陋的文章.
我們要獲取這個JSContext,可以通過KVC :
JSContext *context = [_webview valueForKeyPath: @"documentView.webView.mainFrame.javaScriptContext"];
但是UIWebView中的這個JSContext是一直在變化的,我們通過觀察,可以發(fā)現(xiàn),在UIWebViewDelegate的三個狀態(tài)中shouldStartLoadWithRequest , webViewDidStartLoad 和 webViewDidFinishLoad時,UIWebView的JSContext都是指向不同地址,對于這個問題,我們一開始是選取最后一個狀態(tài),即webViewDidFinishLoad中的JSContext來使用,因為這個JSContext也是UIWebView加載結(jié)束后一直使用的JSContext.所以我們設(shè)置一個delegateproxy對象,以獲取webViewDidFinishLoad事件,在此時將所需的js注入到從UIWebView中獲取的JSContext中,就可以賦予JS與OC交互的功能,而這個階段的主要操作就是 :
// 獲取js執(zhí)行環(huán)境 JSContext *context = [_webview valueForKeyPath: @"documentView.webView.mainFrame.javaScriptContext"]; // 注入bridge.JS [_context evaluateScript:[MXWebviewContext shareContext].bridgeJS]; // 從js環(huán)境中獲取 JSbridgeForOC, 在MXWebviewBridge中持有 _jsBridge = [_context[@"mxbridge"] valueForProperty:@"JSbridgeForOC"]; // 將MXWebviewBridge放入js的環(huán)境中,由mxbridge持有 [_context[@"mxbridge"] setValue:self forProperty:@"OCBridgeForJS"];
但由于webViewDidStartLoad的限制,我們的mxbridge必須在頁面加載完成后,才會初始化完成,而js有些代碼會在頁面加載過程中執(zhí)行,為了處理這個時間差,我們有一個變量來表示mxbridge的加載狀態(tài),即mxbridge.isReady, 還有一個bridgeReady的Event會在初始化完成時發(fā)送出去.所以js調(diào)用插件時,首先需要檢測mxbridge.isReady,如果mxbridge沒有成功初始化,就需要等待bridgeReady事件發(fā)生了. 如:
execSafely : function (pluginName, functionName, args,successCallback,failCallback) { if (window.mxbridge && window.mxbridge.isReady) { window.mxbridge.exec(pluginName, functionName, args,successCallback,failCallback); } else { document.addEventListener("bridgeReady", function() { window.mxbridge.exec(pluginName, functionName, args,successCallback,failCallback); }, true); } },
繼續(xù)討論實現(xiàn)原理,剛才說到初始化js環(huán)境,OC端持有一個JS的橋,而JS端也持有了一個OC端的橋,這樣我們就可以使用JavaScriptCore相關(guān)的知識進行兩者之間的交互了.
Objective-C供JavaScript持有一個MXWebviewBridge對象,而這個對象實現(xiàn)了一個繼承了JSExport協(xié)議的MXNativeBridgeExport ,繼承JSExport后,可以將OC中的方法直接在JS中使用,所以提供了三個接口給JS使用:
// 在Native端打日志,方便調(diào)試 -(void)loggerWithLevel:(NSArray *)arguments; // 異步調(diào)用插件 -(void)callAsyn:(NSDictionary *)arguments; // 同步調(diào)用插件 -(JSValue *)callSync:(NSDictionary *)arguments;
JavaScript通過callAsyn:和callSync:調(diào)用OC提供的插件,這兩個函數(shù)中的具體實現(xiàn),也比較簡單,以callAsyn:舉例說明一下:
-(void)callAsyn:(NSDictionary *)arguments { dispatch_async(dispatch_get_main_queue(), ^{ // 在主線程中執(zhí)行。 MXMethodInvocation *invocation = [[MXMethodInvocation alloc] initWithJSCall:arguments]; if (invocation == nil) { NSDictionary *error = @{@"errorCode":MXBridge_ReturnCode_PLUGIN_INIT_FAILED,@"errorMsg":@"傳遞參數(shù)錯誤,無法調(diào)用函數(shù)!"}; NSLog(@"異步調(diào)用 ,失敗 %@",error); } MXWebviewPlugin *plugin = _pluginDictionarys[invocation.pluginName]; if (!plugin) { Class cls = [MXWebviewContext shareContext].plugins[invocation.pluginName]; if (cls == NULL) { NSDictionary *error = @{@"errorCode":MXBridge_ReturnCode_PLUGIN_NOT_FOUND,@"errorMsg":[NSString stringWithFormat:@"插件 %@ 并不存在 ",invocation.pluginName]}; [self callBackSuccess:NO withDictionary:error toInvocation:invocation]; } plugin = [[cls alloc] initWithBridge:self]; _pluginDictionarys[invocation.pluginName] = plugin; } // 調(diào)用 插件中相應(yīng)方法 SEL selector = NSSelectorFromString(invocation.functionName); if (![plugin respondsToSelector:selector]) { selector = NSSelectorFromString([invocation.functionName stringByAppendingString:@":"]); if (![plugin respondsToSelector:selector]) { NSDictionary *error = @{@"errorCode":MXBridge_ReturnCode_METHOD_NOT_FOUND_EXCEPTION,@"errorMsg":[NSString stringWithFormat:@"插件對應(yīng)函數(shù) %@ 并不存在 ",invocation.functionName]}; [self callBackSuccess:NO withDictionary:error toInvocation:invocation]; } } // 調(diào)用插件 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [plugin performSelector:selector withObject:invocation]; #pragma clang diagnostic pop }); }
上段代碼中,當JS調(diào)用OC的數(shù)據(jù)傳到后,先將調(diào)用數(shù)據(jù)轉(zhuǎn)換為一個MXMethodInvocation對象,然后檢測參數(shù)合法性. 然后檢測插件是否存在,不存在則去創(chuàng)建插件,但插件不在plugins.plist中或者類不存在,也會有相應(yīng)地錯誤提示.拿到插件后,就可以根據(jù)方法名和js傳遞的參數(shù)調(diào)用插件相應(yīng)地方法了.
對于異步調(diào)用的插件,js調(diào)用時,會傳遞調(diào)用成功和失敗的回調(diào) :
var list = { "success":successCallback, "fail":failCallback }; mxbridge.JSbridgeForOC.callBackLists[jscall.invocationID] = list;
bridge將成功失敗的回調(diào)以 一次調(diào)用的唯一標示記錄在JSbridgeForOC, 而在異步回調(diào)JavaScript的處理函數(shù)時,也就是調(diào)用JSbridgeForOC的callbackAsyn方法時,就會從callBackLists中找到對應(yīng)的回調(diào)函數(shù),以執(zhí)行相應(yīng)的回調(diào):
callbackAsyn : function (callbackID,status,args) { // 執(zhí)行異步調(diào)用,然后OC對JS的調(diào)用立即返回。 window.setTimeout(function() { mxbridge.JSbridgeForOC._callbackAsyn(callbackID,status,args); },0); }, // 真正的回調(diào)函數(shù). _callbackAsyn : function(callbackID , status ,args) { var callbackfuns = mxbridge.JSbridgeForOC.callBackLists[callbackID]; if (callbackfuns) { if (status == mxbridge.OK) { callbackfuns.success && callbackfuns.success(args); } else { callbackfuns.fail && callbackfuns.fail(args); } delete mxbridge.JSbridgeForOC.callBackLists[callbackID]; }; }使用步驟
導(dǎo)入代碼.
創(chuàng)建插件 ,插件的編寫要注意以下幾點 :
繼承 MUWebviewPlugin 類,這個類中提供了幾個在插件中常用的屬性,bridge,containerVC和webView,一些異步時的回調(diào)函數(shù),如- (void)callBackSuccess:(BOOL)success withDictionary:(NSDictionary *)dict toInvocation:(MUOCMethodInvocation *)invocation; 和 - (void)callBackSuccess:(BOOL)success withString:(NSString *)string toCallbackID:(NSString *)callbackID; ,返回給JS的值,可以是一個字符串,也可以是以NSDictionary表示的
JSON對象.
- (instancetype)initWithBridge:(MUWebviewBridge *)bridge,可以在這個初始化函數(shù)中作一些初始化的操作.
對于插件方法,形式是這樣的 : - (NSDictionary *)syncFunction(:(MUOCMethodInvocation *)invocation); ,同步方法返回值必須是 NSDictionary * ,而參數(shù)可以有也可以沒有. 對于異步方法 - (void)asynFunction(:(MUOCMethodInvocation *)invocation),返回值類型為void,參數(shù)也可以有,可以沒有. 傳遞的參數(shù)放在MUOCMethodInvocation中.
創(chuàng)建 plugins.plist文件,以 鍵值對的形式,插件名和插件類名的對應(yīng)關(guān)系.
在需要該功能時,調(diào)用 MUWebviewContext的setUp方法,激活功能,使項目中所有的webview都能進行交互.
在MUWebViewContext中提供了幾個接口,以供設(shè)置 :
appName,appVersion,osType,osVersion ,等應(yīng)用系統(tǒng)信息.
loggerBlock,一個打日志的block,用于調(diào)試JS..
注意事項JS調(diào)用插件,傳遞的參數(shù)是json對象的形式.而調(diào)用參數(shù)傳遞到插件中時,是以NSDictionary的形式.同理,在回調(diào)JS時,OC傳遞的類型是NSDictionary,而到達JS的返回值是 json對象. 這與JavaScriptCore相關(guān).
在UIWebView頁面加載完成時,才會初始化MXBridge以支持插件調(diào)用功能,所以,調(diào)用插件前,要進行檢測,以確保mxbridge已經(jīng)初始化完成.
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/79846.html
摘要:精讀前端可以從多個角度理解,比如規(guī)范框架語言社區(qū)場景以及整條研發(fā)鏈路。同是前端未來展望,不同的文章側(cè)重的格局不同,兩個標題相同的文章內(nèi)容可能大相徑庭。作為使用者,現(xiàn)在和未來的主流可能都是微軟系,畢竟微軟在操作系統(tǒng)方面人才儲備和經(jīng)驗積累很多。 1. 引言 前端展望的文章越來越不好寫了,隨著前端發(fā)展的深入,需要擁有非常寬廣的視野與格局才能看清前端的未來。 筆者根據(jù)自身經(jīng)驗,結(jié)合下面幾篇文章...
摘要:在上有這樣一個項目可以拿到了上下文創(chuàng)建的事件,只不過也是改獲取方法也是蘋果的私有,原來項目中使用了這個庫上架蘋果應(yīng)用商店沒有問題,現(xiàn)在審核情況不太了解。 前言 動態(tài)化是移動開發(fā)技術(shù)中的重要的一部分 ,當前普遍的動態(tài)化方案 , 如 React Native 、Weex 、Hybrid部分解決方案及之前流行的熱修復(fù)框架 JSPatch ,背后都用到了 JavaScriptCore 框架 ,...
摘要:在中,我們可以引入框架,這樣,我們可以層來操作層代碼的執(zhí)行。都會發(fā)送相應(yīng)的消息給。在端,由于只有暴露在全局的函數(shù)聲明才能夠讓端訪問,這就限制了端的靈活性。我們有理由憧憬未來在和下更方便的集成引擎來完成建議的雙向通信。 JavaScriptCore引擎 ????我們都知道WebKit是個渲染引擎,簡單來說負責頁面的布局,繪制以及層的合成,但是WebKit工程中不僅僅有關(guān)于渲染相關(guān)的邏輯,...
摘要:否則按照正常流程處理。如果是表示是初始化環(huán)境的消息,如果是則表示是發(fā)送消息。則立即發(fā)送消息?;卣{(diào)主動調(diào)用獲取注冊的函數(shù)調(diào)用中的對應(yīng)函數(shù)處理把消息從發(fā)送到,執(zhí)行具體的發(fā)送操作。處理從返回的消息。從而找到具體的實現(xiàn)執(zhí)行。 基本說明 我們的項目是一個OC與javascript重度交互的app,OC與javascript交互的那部分是在WebViewJavascriptBridge的githu...
閱讀 1280·2021-09-27 13:35
閱讀 2581·2021-09-06 15:12
閱讀 3395·2019-08-30 15:55
閱讀 2853·2019-08-30 15:43
閱讀 444·2019-08-29 16:42
閱讀 3458·2019-08-29 15:39
閱讀 3075·2019-08-29 12:28
閱讀 1254·2019-08-29 11:11