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