上篇中,咱們經過簡單的kvc獲取UIWebVIew的JSContext,可是實際上,apple並未給開發者提供訪問UIWebView的方法,雖然經過KVC可達到目標,可是當APP採用該種hack方法時,有很大概率不能經過APP Store的審覈,這對於一個基於上線的商業APP而言是難以忍受的,因此咱們必須尋找另外一種方法來獲取UIWebView的JSContext並且足夠安全易用,所以咱們需轉移目光。javascript
在OS X中,WebFrameLoadDelegate負責WebKit與NSWebView的通訊,因爲NSWebView內部仍然使用WebKit渲染引擎,若要偵聽渲染過程當中的一系列事件,則必須使用WebFrameLoadDelegate對象:
一、加載過程:
在一個訪問一個網頁的的整個過程,包括開始加載,加載標題,加載結束等。webkit都會發送相應的消息給WebFrameLoadDelegate 。java
webView:didStartProvisionalLoadForFrame:開始加載,在這裏獲取加載的url webView:didReceiveTitle:forFrame:獲取到網頁標題 webView:didFinishLoadForFrame:頁面加載完成
二、錯誤的處理:
加載的過程中,有可能會發生錯誤。錯誤的消息也會發送給WebFrameLoadDelegate 。咱們能夠在這兩個函數裏面對錯誤信息進行處理android
webView:didFailProvisionalLoadWithError:forFrame: 這個錯誤發生在請求數據以前,最多見是發生在無效的URL或者網絡斷開沒法發送請求 webView:didFailLoadWithError:forFrame: 這個錯誤發生在請求數據以後
但是在iOS中呢?我嘗試過,並無WebFrameLoadDelegate這個對象,看來iOS中的WebKit框架並未提供UIWebView這麼多的接口,可是有些人經過WebKit的源碼仍是發現了一二,他就是Nick Hodapp。web
在iOS中,儘管沒有暴露WebFrameLoadDelegate,可是在具體實現上仍會判斷WebKit的implement有沒有實現這個協議的某些方法,若是實現則仍會執行,並且在webit的WebFrameLoaderClient.mm文件中,安全
if (implementations->didCreateJavaScriptContextForFrameFunc) { CallFrameLoadDelegate(implementations->didCreateJavaScriptContextForFrameFunc, webView, @selector(webView:didCreateJavaScriptContext:forFrame:), script.javaScriptContext(), m_webFrame.get()); }
會判斷當前的對象有沒有實現webView:didCreateJavaScriptContext:forFrame:
方法,有則執行。該方法會傳遞三個參數,第一個是與webkit通訊的WebView(此WebView並非UIWebVIew,Nick層作過測試經過獲取的WebView並不能遍歷到咱們須要的UIWebVIew,所以推測,這個WebView是一個UIView的proxy對象,不是UIView類);第二個則是咱們想要獲取的JSContext;第三個參數是webkit框架中的WebFrame對象,與咱們的指望無關。網絡
爲了讓webkit執行這個函數,咱們必須讓對象實現這個方法。因爲全部的OC對象都繼承自NSObject對象,所以咱們能夠在NSObject對象上實現該方法,這樣能夠保證該段代碼能夠在webkit框架中執行。app
其次,咱們既然獲取到了JSContext,可是並不知道JSContext與UIWebVIew的對應關係,咱們的ViewController中可能會有多個UIWebView,如何將獲取的JSContext與UIWebview對應起來也是一個難題。在此處有一個簡單的方法,就是獲取全部的UIWebView對象,在每一個對象中執行一段js代碼,在js上下文設置一個變量作爲標記,而後在咱們獲取的JSContext中判斷該變量是否與遍歷的UIWebVIew對象中的對象是否相等來獲取。這樣,咱們能夠在UIWebView的webViewDidStartLoad和webViewDidFinishLoad之間獲取到JSContext,進行oc和js的雙向通訊。框架
咱們經過上節的闡述,大體明白了Nick的思路,所以能夠經過協議和類別來完成這種通訊機制,固然採用oc運行時也是能夠的。最終oc端接口的代碼放在webView:didCreateJavaScriptContext:forFrame:
中,這樣js文件只需加載完畢就可執行oc的接口方法;而oc端要訪問js的接口則可在webVIewDidFinishLoad中執行,完美解決接口訪問時機
的問題。
在js端,因爲只有暴露在全局的函數聲明
纔可以讓oc端訪問,這就限制了js端的靈活性。我嘗試過在js端經過「賦值」完成接口的暴露(window.say = function(){alert("hello world!")};),在oc端沒法訪問,只有經過普通的函數聲明才能解決問題,這可能與JSContext的內存指針引用相關,爲了解決此問題,我經過建立一個全局函數來暴露js端的接口對象,經過獲取的對象來訪問具體的接口方法。函數
if(isiOS4JSC){ // 將註冊的方法透出到window.jscObj的屬性上 var ev = eval; $.JSBridge._JSMethod = method; // 暴露函數至全局 // jsc只能執行全局函數聲明方式定義的函數,不能夠將函數指針複製給其餘變量執行 ev('function toObjectCExec() {' + 'window.jscObj = window.jscObj ? window.jscObj : {};'+ 'window.jscObj["' + methodName + '"] = function (message) {' + ' var ret = $.JSBridge._JSMethod(message);' + ' return JSON.stringify(ret);' + '};' + 'return jscObj;' + '}'); }
如此,js端的接口暴露就很容易了。測試
我如今仍然相信,目前的iOS hybridAPP的主流通訊方式仍然適corava的javascriptWebViewBridge,可是隨着jsc引入到iOS7中,本文介紹的使用jsc(嵌入js引擎的方式)來完成oc和js的通訊將更爲流行,儘管目前apple提供的針對jsc的開發接口文檔幾乎沒有,可是咱們經過webkit的源碼作一些hack的方式也不是不能夠,畢竟只要UIWebView仍然使用webkit進行渲染,這種方式會一直有效,除非apple在代碼層面針對hack作過濾,不過這種可能性真的很小。咱們有理由憧憬將來在iOS和android下更方便的集成js引擎來完成建議的雙向通訊。