JavaScriptCore是iOS 7之后蘋果推出的用于OC和swift與JS交互的框架,該框架提供了JS運行環境,數據轉換以及調用協議等實用且強大的功能。
但是相比WebViewJavaScriptBridge等第三方框架在使用過程中坑還是較多,使用人數較少;如項目中不想使用第三方hybrid交互框架的話,JavaScriptCore也是個不錯的選擇。
一.簡介
首先導入頭文件:
#import <JavaScriptCore/JavaScriptCore.h>
點進去可以看到框架的主要成員
#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"
1.JSVirtualMachine
一個 JSVirtualMachine 實例代表一個執行 JavaScript 的自包含(self-contained)的環境,即虛擬機;它是橋接 JavaScript 和 Objective-C 或 Swift 的基礎。
2.JSContext
JSContext為存儲JS代碼的上下文,在創建的時候會依賴于一個JSVirtualMachine實例:
- (instancetype)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;
如在初始化時直接init,系統也會初始化一個JSVirtualMachine實例,即虛擬機。一個虛擬機可以包含多個JSContext,在同一個虛擬機下的不同JSContext才可以相互傳值,而且JavaScriptCore的API是線程安全的,同一個虛擬機中所有線程只能串行執行,如想實現并發操作就只能通過創建多個虛擬機來實現。
3.JSValue
JSValue為JavaScript 和 Objective-C 或 Swift數據類型轉換的橋梁,它提供了多種數據類型的轉換方法,數據類型對比如下圖:
4.JSManagedValue
將 JSValue 轉為 JSManagedValue 類型后,可以添加到 JSVirtualMachine 對象中,這樣能夠保證你在使用過程中 JSValue 對象不會被釋放掉,當你不再需要該 JSValue 對象后,從 JSVirtualMachine 中移除該 JSManagedValue 對象,JSValue 對象就會被釋放并置空
5.JSExport
JSExport是一個可以連接JavaScript 和 Objective-C 或 Swift 的強大協議,將實現了該協議的對象傳遞給JS,那么JS就可以調用該對象的方法,從而實現兩者之間的交互。
二.實際運用
1.在網頁加載完成之后的初始化工作
- (void)webViewDidFinishLoad:(UIWebView *)webView {
//在網頁加載完成之后,初始化JSContext
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//捕獲JS異常
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
[JSContext currentContext].exception = exception;
NSLog(@"exception:%@",exception);
};
}
2.調用JS的函數
// 計算兩數之和
JSValue *value1 = [self.jsContext evaluateScript:@"add(1,2)"];
// 也可以通過下標的方式獲取到方法
JSValue *add = self.jsContext[@"add"];
JSValue *value2 = [add callWithArguments:@[@"1",@"2"]];
3.生成JS函數
self.jsContext[@"add"] = ^() {
//拿到參數數組
NSArray *args = [JSContext currentArguments];
double sum = 0;
for (JSValue *value in args) {
double num = [value toDouble];
sum += num;
}
return sum;
}
4.通過JSExport進行交互
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
//定義一個JSExport protocol
@protocol JSExportTest <JSExport>
JSExportAs(add, - (double)addWithNumberArray:(NSArray *)numberArray ;
@end
//創建一個類遵循JSExport協議
@interface JSBridge : NSObject<JSExportTest>
@end
//實現協議方法
#import "JSBridge.h"
@implementation JSBridge
- (double)addWithNumberArray:(NSArray *)numberArray {
double sum = 0;
for (double number in numberArray) {
sum += number;
}
return sum;
}
//網頁加載完畢之后注入交互對象
- (void)webViewDidFinishLoad:(UIWebView *)webView {
//在網頁加載完成之后,初始化JSContext
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//捕獲JS異常
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
[JSContext currentContext].exception = exception;
NSLog(@"exception:%@",exception);
};
//網頁加載完畢之后注入交互對象
self.jsContext[@"iOSBridge"] = self.jsBridge;
}
//JS端使用示例:
function test() {
var array=new Array(1,2,3),sum;
sum = iOSBridge.add(array);
alert(sum);
}
//還可以利用runtime為已有類添加協議
@protocol JSUITextFieldExport <JSExport>
@property(nonatomic,copy) NSString *jsText;
@end
- (void)viewDidLoad {
[super viewDidLoad];
class_addProtocol([UITextField class], @protocol(JSUITextFieldExport));
self.jsContext[@"iOSTextField"] = self.textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
textField.jsText = textField.text;
}
//JS端使用示例:
function test() {
alert(iOSTextField.jsText);
}
三.注意事項
1.Block
無論是把Block傳給JSContext對象讓其變成JavaScript方法,還是把它賦給exceptionHandler屬性,在Block內都不要直接使用其外部定義的JSContext對象或者JSValue,應該將其當做參數傳入到Block中,或者通過JSContext的類方法+ (JSContext *)currentContext;來獲得。否則會造成循環引用使得內存無法被正確釋放。
2.線程問題
線程問題可以看這篇文章:http://www.lxweimin.com/p/d616aebf3f14
最后的評論中也有我遇到的問題,線程問題到現在還沒有總結分析完整,在此列出只為讓各位參考,如有先發現還望告知,不勝感謝!
3.不足
使用JavaScriptCore時,必須首先加載完JS代碼,創建JSContext才能進行下一步操作,這就出現了一個很大的不足:JS端在初始化時無法調用OC或Swift代碼。在開發中,我采用了等JS加載完成并注入橋接對象之后調用JS函數的方式來完成JS想在初始化時完成的交互操作,但此處需注意線程問題。
四.總結
在最近的一年的工作中陸續用了JavaScriptCore和WebViewJavaScriptBridge,正如開篇所說,在不考慮第三方框架的劣勢的情況下,還是WebViewJavaScriptBridge更加便利,因此隨后我也將對WebViewJavaScriptBridge的使用和原理進行一次總結。對WebViewJavaScriptBridge的使用和原理感興趣的朋友可以點擊這里