JavaScriptCore 基本使用
現在的移動開發已經越來越傾向于使用混合開發,而要使用混合開發就要求我們必須能讓原生與 JavaScript 進行無縫的交互。
在 iOS 7 之前,我們對 JavaScript 的操作只有 UIWebView 里的一個方法 stringByEvaluatingJavaScriptFromString ,而從 Javascript 里面調用原生方法只能使用攔截 URL 的方法,類似 WebViewJavascripBridge 就是基于 URL 攔截的原理來進行實現的。
而從 iOS 7 開始,蘋果把 JavascriptCore 引進到了 iOS 開發當中,這個框架可以讓我們脫離 UIWebView 同時用更方便同時更強大的方法來與 Javascript 進行交互。
JavaScriptCore 總覽
在學習 JavaScriptCore 的使用之前,需要先了解 JavaScriptCore 當中的重要類型以及協議,包括 JSValue 、 JSContext 、 JSVirtualMachine 、 JSManagedValue 以及 JSExport 。
JSVirtualMachine
Javascript 代碼是在虛擬機當中運行的,每一個虛擬機由一個 JSVirtualMachine 來表示。一般情況下我們不用去手動創建 JSVirtualMachine 實例,使用系統提供的就足夠了。
需要手動創建 JSVirtualMachine 的一個主要場景就是當我們需要并發地運行 Javascript 代碼時,在單一的 JSVirtualMachine 里面是沒辦法 同時 運行多個線程的。
JSContext
代表 JavaScript 的運行環境,是一個全局對象,可以理解為 Web 開發中的 window 對象。所有的 JSValue 都與 JSContext 相關聯。
JSValue
用來表示 JavaScript 實體的本地對象。由于 JavaScript 是弱類型的語言,所以我們使用 JSValue 就可以代表所有 Javascript 中的類型。每個 JSValue 實例都與它對應的 JSContext 相關聯。所以從 context 對象中產生的值都是 JSValue 類型的。
JSManagedValue
Objective-C 或者 Swift 的對象都是使用引用計數,而 Javascript 則是使用垃圾回收機制。為了避免兩種語言交互時產生的循環引用,需要使用 JSManagedValue 進行內存輔助管理。
JSExport
這是一個協議,使用這個協議可以將本地的對象導出對 JavaScript 對象,所有的本地屬性與方法都會直接變成 JavaScript 的屬性與方法。可以,這很魔法。
Objective-C 與 Javascript 之間的交互
從 Objective-C 調用 Javascript 有一種方法,而使用 JavaScript 調用原生則有兩種方法,下面分別進行介紹。
原生調用 Javascript
+ (BOOL)isValidNumber:(NSString *)phone
{
// 1. getting a JSContext
JSContext *context = [[JSContext alloc] init];
// 2. defining a JavaScript function
NSString *jsFunctionText =
@"var isValidNumber = function(phone) {"
" var phonePattern = /^[0-9]{3}[ ][0-9]{3}[-][0-9]{4}$/;"
" return phone.match(phonePattern) ? true : false;"
"}";
[context evaluateScript:jsFunctionText];
// 3. calling a JavaScript function
JSValue *jsFunction = context[@"isValidNumber"];
JSValue *value = [jsFunction callWithArguments:@[ phone ]];
return [value toBool];
}
使用 Objective-C 調用 JavaScript 的很簡單:
- 創建一個 JSContext 對象
- 將 JavaScript 代碼加載到 context 當中
- 取到 JavaScript 函數對象,并使用 callWithArguments: 進行傳參調用
JavaScript 調用原生
使用 JavaScript 調用原生有兩種方法,使用閉包或者 JSExport 協議。
使用閉包
// 1.
JSContext *context = [[JSContext alloc] init];
// 2.
void (^block)() = ^(NSString *string) {
NSLog(@"%@", string);
};
// 3.
[context setObject:block forKeyedSubscript:@"print"];
// 4.
[context evaluateScript:@"print('Hello World');"];
使用閉包來調用原生方法也很簡單:
- 跟之前一樣,首先需要先創建一個 JSContext
- 定義一個閉包
- 將閉包保存到 context 當中,也就是轉換成了 JavaScript 的方法。這里使用了 setObject:forKeydSubscript 方法,其實也可以直接使用下標操作, context[@"print"] = block; 。
- 在 JavaScript 當中調用,會直接調用到原生里面的方法。
使用 JSExport
首先,定義一個繼承于 JSExport 的協議:
@protocol ViewControllerJSExport <JSExport>
- (void)print:(NSString *)message;
@end
然后,讓需要在 JavaScript 中調用的那個類遵守這個協議,在這里簡單地使用 ViewController:
@interface ViewController () <ViewControllerJSExport>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
JSContext *context = [[JSContext alloc] init];
// 1.
context[@"myApp"] = self;
// 2.
[context evaluateScript:@"myApp.print('Hello World');"];
}
- (void)print:(NSString *)message {
NSLog(@"%@", message);
}
@end
在這里需要注意的是,我們將類的句柄傳給了 context(在這里是直接將 viewController 傳過去),然后在 JavaScript 中就需要使用這個句柄來進行調用。
捕獲異常
JavaScriptCore 允許我們使用一個 Objective-C 閉包來對 JavaScript 中的異常進行捕獲:
[context setExceptionHandler:^(JSContext *context, JSValue *value) {
NSLog(@"%@", value);
}];
只要 JavaScript 中產生了異常,異常信息就會被傳遞到這個閉包當中,我們可以對其進行處理。建議始終實現這個閉包,對于 JavaScript 中出現的異常很難被發現,就算只是單純地將錯誤信息打印出來,也可以節省我們大量調試錯誤的時間的精力。
小結
這里只是介紹了最基本的 JavaScriptCore 的使用方法,只能說開了個頭,實際使用 JavaScriptCore 其實也有很多坑,包括內存管理,JavaScript 代碼的調試。
同時,我們也不會經常直接加載本地的 JavaScript 代碼,大多數情況下,都是需要直接與 WebView,與前端的 JavaScript 代碼進行交互,這時就需要直接獲取 UIWebView 的 JSContext。
還有在 iOS 8 后推出來的 WKWebView 沒有辦法直接獲取它的 JSContext,它使用了另一套方面來與 JavaScript 進行交互。
而這些都是在開發中都會碰到的實際問題,所以這些都會在之后的文章中進行詳細介紹(又開一個坑)。
參考資料
- JavaScriptCore by Example
- JavaScriptCore Tutorial for iOS: Getting Started
- iOS 7 by Tutorials
來自:http://www.swiftyper.com/2016/08/22/javascriptcore-basic/