Hybrid~iOS與JS交互那點事兒之JavaScriptCore

圖片來源于網絡(侵刪).jpg

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數據類型轉換的橋梁,它提供了多種數據類型的轉換方法,數據類型對比如下圖:


JSValue類型轉換.png
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的使用和原理感興趣的朋友可以點擊這里

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容