JavaScriptCore 使用

發表於2016-08-03

JavaScriptCore

JavaScriptCore是webkit的一個重要組成部分,主要是對JS進行解析和提供執行環境。程式碼是開源的,可以下下來看看(原始碼)。iOS7後蘋果在iPhone平臺推出,極大的方便了我們對js的操作。我們可以脫離webview直接執行我們的js。iOS7以前我們對JS的操作只有webview裡面一個函式 stringByEvaluatingJavaScriptFromString,JS對OC的回撥都是基於URL的攔截進行的操作。大家用得比較多的是WebViewJavascriptBridgeEasyJSWebView這兩個開源庫,很多混合都採用的這種方式。

JavaScriptCore和我們相關的類不是很多,使用起來也非常簡單。

JSContext

JS執行的環境,同時也通過JSVirtualMachine管理著所有物件的生命週期,每個JSValue都和JSContext相關聯並且強引用context。

JSValue

JS物件在JSVirtualMachine中的一個強引用,其實就是Hybird物件。我們對JS的操作都是通過它。並且每個JSValue都是強引用一個context。同時,OC和JS物件之間的轉換也是通過它,相應的型別轉換如下:

JSManagedValue

JS和OC物件的記憶體管理輔助物件。由於JS記憶體管理是垃圾回收,並且JS中的物件都是強引用,而OC是引用計數。如果雙方相互引用,勢必會造成迴圈引用,而導致記憶體洩露。我們可以用JSManagedValue儲存JSValue來避免。

JSVirtualMachine

JS執行的虛擬機器,有獨立的堆空間和垃圾回收機制。

JSExport

一個協議,如果JS物件想直接呼叫OC物件裡面的方法和屬性,那麼這個OC物件只要實現這個JSExport協議就可以了。

OC和JS之間的通訊

兩者之間的通訊還是很簡單的,直接看簡單程式碼示例吧。

Objective-C -> JavaScript

步驟很簡單,建立一個JSContext物件,然後將JS程式碼載入到context裡面,最後取到這個函式物件,呼叫callWithArguments這個方法進行引數傳值。(JS裡面函式也是物件)

JavaScript -> Objective-C

JS呼叫OC有兩個方法:block和JSExport protocol。

block(JS function):

我們定義一個block,然後儲存到context裡面,其實就是轉換成了JS的function。然後我們直接執行這個function,呼叫的就是我們的block裡面的內容了。

JSExport protocol:

demo很簡單,還是定義了一個兩個數相加的方法,還有一個儲存結果的變數。在JS中進行呼叫這個物件的方法,並將結果賦值sum。唯一要注意的是OC的函式命名和JS函式命名規則問題。協議中定義的是add: b:,但是JS裡面方法名字是addB(a,b)。可以通過JSExportAs這個巨集轉換成JS的函式名字。

修改下程式碼:

我們可以定義自己的異常捕獲,可以把context,異常block改為自己的:

記憶體管理

現在來說說記憶體管理的注意點,OC使用的ARC,JS使用的是垃圾回收機制,並且所有的引用是都強引用,不過JS的迴圈引用,垃圾回收會幫它們打破。JavaScriptCore裡面提供的API,正常情況下,OC和JS物件之間記憶體管理都無需我們去關心。不過還是有幾個注意點需要我們去留意下。

1、不要在block裡面直接使用context,或者使用外部的JSValue物件。

這個程式碼,不用自己看了,編譯器都會提示你的。這個block裡面使用self,很容易就看出來了。

這個是block裡面使用了外部的value,value對context和它管理的JS物件都是強引用。這個value被block所捕獲,這邊同樣也會記憶體洩露,context是銷燬不掉的。

2、OC物件不要用屬性直接儲存JSValue物件,因為這樣太容易迴圈引用了。

看個demo,把上面的示例改下:

上面的例子很簡單,呼叫JS方法,進行賦值,JS物件保留了傳進來的obj,最後,JS將自己的回撥callback賦值給了obj,方便obj下次回撥給JS;由於JS那邊儲存了obj,而且obj這邊也保留了JS的回撥。這樣就形成了迴圈引用。

怎麼解決這個問題?我們只需要打破obj對JSValue物件的引用即可。當然,不是我們OC中的weak。而是之前說的記憶體管理輔助物件JSManagedValue

JSManagedValue 本身就是我們需要的弱引用。用官方的話來說叫garbage collection weak reference。但是它幫助我們持有JSValue物件必須同時滿足一下兩個條件(不翻譯了,翻譯了怪怪的!):

  • The JSManagedValue’s JavaScript value is reachable from JavaScript
  • The owner of the managed reference is reachable in Objective-C. Manually adding or removing the managed reference in the JSVirtualMachine determines reachability.

意思很簡單,JSManagedValue 幫助我們儲存JSValue,那裡面儲存的JS物件必須在JS中存在,同時 JSManagedValue 的owner在OC中也存在。我們可以通過它提供的兩個方法 + (JSManagedValue )managedValueWithValue:(JSValue )value;

  • (JSManagedValue )managedValueWithValue:(JSValue )value andOwner:(id)owner建立JSManagedValue物件。通過JSVirtualMachine的方法- (void)addManagedReference:(id)object withOwner:(id)owner來建立這個弱引用關係。通過- (void)removeManagedReference:(id)object withOwner:(id)owner 來手動移除他們之間的聯絡。

把剛剛的程式碼改下:

注:以上程式碼只是為了突出用 JSManagedValue來儲存 JSValue,所以重寫了 setter 方法。實際不會寫這麼搓的姿勢。。。應該根據回撥方法傳進來引數,進行儲存 JSValue

3、不要在不同的 JSVirtualMachine 之間進行傳遞JS物件。

一個 JSVirtualMachine可以執行多個context,由於都是在同一個堆記憶體和同一個垃圾回收下,所以相互之間傳值是沒問題的。但是如果在不同的 JSVirtualMachine傳值,垃圾回收就不知道他們之間的關係了,可能會引起異常。

執行緒

JavaScriptCore 執行緒是安全的,每個context執行的時候通過lock關聯的JSVirtualMachine。如果要進行併發操作,可以建立多個JSVirtualMachine例項進行操作。

與UIWebView的操作

通過上面的demo,應該差不多瞭解OC如何和JS進行通訊。下面我們看看如何對 UIWebView 進行操作,我們不再通過URL攔截,我們直接取 UIWebViewcontext,然後進行對JS操作。

UIWebView的finish的回撥中進行獲取

上面用了私有屬性,可能會被蘋果給拒了。這邊要注意的是每個頁面載入完都是一個新的context,但是都是同一個JSVirtualMachine。如果JS呼叫OC方法進行操作UI的時候,請注意執行緒是不是主執行緒。

參考:

http://blog.csdn.net/lizhongfu2013/article/details/9232129

https://developer.apple.com/videos/play/wwdc2013-615/

相關文章