淺談 JavaScriptCore

發表於2016-10-14

本文由我們團隊的王瑞華童鞋撰寫。


OS X Mavericks 和 iOS 7 引入了 JavaScriptCore 庫,它把 WebKit 的 JavaScript 引擎用 Objective-C 封裝,提供了簡單,快速以及安全的方式接入世界上最流行的語言。在專案的實際開發中,由於需要與 H5 端有互動,所以採用了JavaScriptCore的互動方式,藉此參與該專案的機會,對JavaScriptCore也有了一些簡單的瞭解。

JSContext/JSValue

JSContext 是 JavaScript 的執行環境。所有 JavaScript 執行發生的背景,以及所有 JavaScript 值被綁在這一個上下文中。JSContext 類似於 UIWindow 的概念,所有的執行都在改環境中發生:

程式碼最後一行,每個 JSValue 起源於 JSContext 和 持有它的強引用。當一個 JSValue 例項方法創造一個新的 JSValue時,該新 JSValue 起源於同一個 JSContext。 JSValue 包裝了每一個可能的 JavaScript 值:字串和數字;陣列、物件和方法;甚至錯誤和特殊的 JavaScript 值諸如 null 和 undefined。

OBJECTIVE-C TYPE JAVASCRIPT TYPE
nil undefined
NSNull null
NSString string
NSNumber number, boolean
NSDictionary Object object
NSArray Array object
NSDate Date object
NSBlock (1) Function object (1)
id (2) Wrapper object (2)
Class (3) Constructor object (3)

下標支援

作為下標傳遞的物件鍵將被轉換為一個 JavaScript 值, 然後值轉換為一個用作獲取屬性名稱的字串。

該簡易方法對應於以下兩個方法:

呼叫方法

JSValue 包裝了一個 JavaScript 函式,我們可以從 Objective-C 程式碼中使用 Foundation 型別作為引數來直接呼叫該函式。

JavaScript 呼叫

上面說明的 OC 呼叫 JavaScript 的方法。那麼接下來,說明 JavaScript 訪問 OC 定義的物件和方法。我粗淺的認為就是在遵守該協議後,JavaScript 就可以呼叫 OC 實現的方法和屬性等。

讓 JSContext 訪問我們的本地客戶端程式碼的方式主要有兩種:JSExport 協議和block。

JSExport 協議

JSExport 提供一個將 OC 中的類、例項方法和屬性等匯出為 JavaScript 函式的方法。

該協議只包含了一個方法:

即當匯出到 JavaScript 時,重新命名一個 selector。

這就需要熟悉它的做法,當帶有一個或多個引數的 seletor 轉變為 JavaScript 的屬性名字時,在預設情況下一個屬性名字將採用以下方式生成:

  • 所有的冒號將從 Selector 當中移除掉
  • 任何一個後邊跟著冒號的小寫字母開頭的單詞將被改換為大寫。

在這種預設的轉換方式下,一個 selector 如 doFoo:withBar: 將被導為doFooWithBar 。這種預設的改變有可能被 JSExportAs 巨集 所改寫,例如將 doFoo:withBar: 匯出為 doFoo 。實際寫法如下:

請注意 JSExport 巨集只可能適用於帶有一個或更多的引數的selector。

Blocks

當一個 Objective-C block 被賦給 JSContext 裡的一個識別符號,JavaScriptCore 會自動的把 block 封裝在 JavaScript 函式裡。如下:

JSManagedValue 與記憶體管理

由於 block 可以保有變數引用,而且 JSContext 也強引用它所有的變數,為了避免強引用迴圈需要特別小心。OC 採用的是引用計數機制,而 JavaScript採用的垃圾回收機制。基於此項機制,便出現了 JSManagedValue ,它的主要用途是儲存一個 JSValue,這樣可以避免一個保留環的產生。

JavaScript 與 OC 互動的實際例項

在開發專案中,我們採用了 JavaScript 的方式完成了與 Native 的互動,使得比單純的運用 UIWebview 的 stringByEvaluatingJavaScriptFromString: 更加高效。

如上所述,讓 JSContext 訪問我們的本地客戶端程式碼的方式主要有兩種:JSExport 協議和block。在我們專案中主要採用了 JSExport 的方式去實現。

1. JDRWebViewJSExportProtocol 和 JDRWebViewBaseHandler

我們的 JDRWebViewBaseHandler 類實現了 JDRWebViewJSExportProtocol 協議,該協議規定了那些方法或者屬性可在 JavaScript 中使用。

2. JSContext 配置

然後,我們可以用我們已經建立的 JDRWebViewBaseHandler 類,我們需要將其匯出到 JavaScript 環境中。

3.在 H5 頁面中書寫 JavaScript 函式呼叫 OC

結語

通常對於 JavaScript 與 OC 的互動,會採用 JavaScriptCore 包裝一層協議方法,原始一點的方法則是通過擷取 UIWebView 的協議實現。隨著 iOS 8 之後 WKWebview的出現,又出現了一個新的方式,而對於 WKWebView 來說是不支援直接與 JavaScriptCore 互動的。通常採取 WKUserContentController 採用它的協議方法

如果使用 WKWebview 的話,對於 H5 中的 JavaScript 寫法又需要改動為 window.webkit.messageHandlers..postMessage()。WKWebView 對於JavaScript 的呼叫只提供了一種方法:

目前專案中考慮到 應用 WKWebview 與 UIWebview + JavaScriptCore 呼叫方式的差異,暫時放棄了對於 WKWebview的支援。

參考

  1. JavaScriptCore