iOS引入JavaScriptCore引擎框架(二)


為何放棄第一種方案

UIWebView的JSContext獲取

    上篇中,我們通過簡單的kvc獲取UIWebVIew的JSContext,但是實際上,apple並未給開發者提供訪問UIWebView的方法,雖然通過KVC可達到目標,但是當APP采用該種hack方法時,有很大幾率不能通過APP Store的審核,這對於一個基於上線的商業APP而言是難以忍受的,所以我們必須尋找另一種方法來獲取UIWebView的JSContext而且足夠安全易用,因此我們需轉移目光。

解決

WebFrameLoadDelegate

    在OS X中,WebFrameLoadDelegate負責WebKit與NSWebView的通信,由於NSWebView內部仍然使用WebKit渲染引擎,若要偵聽渲染過程中的一系列事件,則必須使用WebFrameLoadDelegate對象:
        1、加載過程:
在一個訪問一個網頁的的整個過程,包括開始加載,加載標題,加載結束等。webkit都會發送相應的消息給WebFrameLoadDelegate 。

              webView:didStartProvisionalLoadForFrame:開始加載,在這里獲取加載的url
              webView:didReceiveTitle:forFrame:獲取到網頁標題
              webView:didFinishLoadForFrame:頁面加載完成

        2、錯誤的處理:
加載的過程當中,有可能會發生錯誤。錯誤的消息也會發送給WebFrameLoadDelegate 。我們可以在這兩個函數里面對錯誤信息進行處理

             webView:didFailProvisionalLoadWithError:forFrame: 這個錯誤發生在請求數據之前,最常見是發生在無效的URL或者網絡斷開無法發送請求
             webView:didFailLoadWithError:forFrame: 這個錯誤發生在請求數據之后

    可是在iOS中呢?我嘗試過,並沒有WebFrameLoadDelegate這個對象,看來iOS中的WebKit框架並未提供UIWebView這么多的接口,但是有些人通過WebKit的源碼還是發現了一二,他就是Nick Hodapp。

Nick的發現

    在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框架中執行。

    其次,我們既然獲取到了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引擎來完成建議的雙向通信。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM