JavaScriptCore 使用

JavaScriptCore

JavaScriptCore是webkit的一個重要組成部分,主要是對JS進(jìn)行解析和提供執(zhí)行環(huán)境。代碼是開源的,可以下下來看看(源碼)。iOS7后蘋果在iPhone平臺推出,極大的方便了我們對js的操作。我們可以脫離webview直接運(yùn)行我們的js。iOS7以前我們對JS的操作只有webview里面一個函數(shù) stringByEvaluatingJavaScriptFromString,JS對OC的回調(diào)都是基于URL的攔截進(jìn)行的操作。大家用得比較多的是WebViewJavascriptBridgeEasyJSWebView這兩個開源庫,很多混合都采用的這種方式。

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

#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"

JSContext

JS執(zhí)行的環(huán)境,同時也通過JSVirtualMachine管理著所有對象的生命周期,每個JSValue都和JSContext相關(guān)聯(lián)并且強(qiáng)引用context。

JSValue

JS對象在JSVirtualMachine中的一個強(qiáng)引用,其實(shí)就是Hybird對象。我們對JS的操作都是通過它。并且每個JSValue都是強(qiáng)引用一個context。同時,OC和JS對象之間的轉(zhuǎn)換也是通過它,相應(yīng)的類型轉(zhuǎn)換如下:

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)

JSManagedValue

JS和OC對象的內(nèi)存管理輔助對象。由于JS內(nèi)存管理是垃圾回收,并且JS中的對象都是強(qiáng)引用,而OC是引用計(jì)數(shù)。如果雙方相互引用,勢必會造成循環(huán)引用,而導(dǎo)致內(nèi)存泄露。我們可以用JSManagedValue保存JSValue來避免。

JSVirtualMachine

JS運(yùn)行的虛擬機(jī),有獨(dú)立的堆空間和垃圾回收機(jī)制。

JSExport

一個協(xié)議,如果JS對象想直接調(diào)用OC對象里面的方法和屬性,那么這個OC對象只要實(shí)現(xiàn)這個JSExport協(xié)議就可以了。

OC和JS之間的通信

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

Objective-C -> JavaScript

     self.context = [[JSContext alloc] init];

    NSString *js = @"function add(a,b) {return a+b}";
    
    [self.context evaluateScript:js];
    
    JSValue *n = [self.context[@"add"] callWithArguments:@[@2, @3]];

    NSLog(@"---%@", @([n toInt32]));//---5

步驟很簡單,創(chuàng)建一個JSContext對象,然后將JS代碼加載到context里面,最后取到這個函數(shù)對象,調(diào)用callWithArguments這個方法進(jìn)行參數(shù)傳值。(JS里面函數(shù)也是對象)

JavaScript -> Objective-C

JS調(diào)用OC有兩個方法:block和JSExport protocol。

block(JS function):

    self.context = [[JSContext alloc] init];
    
    self.context[@"add"] = ^(NSInteger a, NSInteger b) {
        NSLog(@"---%@", @(a + b));
    };
    
    [self.context evaluateScript:@"add(2,3)"];

我們定義一個block,然后保存到context里面,其實(shí)就是轉(zhuǎn)換成了JS的function。然后我們直接執(zhí)行這個function,調(diào)用的就是我們的block里面的內(nèi)容了。

JSExport protocol:

//定義一個JSExport protocol
@protocol JSExportTest <JSExport>

- (NSInteger)add:(NSInteger)a b:(NSInteger)b;

@property (nonatomic, assign) NSInteger sum;

@end

//建一個對象去實(shí)現(xiàn)這個協(xié)議:

@interface JSProtocolObj : NSObject<JSExportTest>
@end

@implementation JSProtocolObj
@synthesize sum = _sum;
//實(shí)現(xiàn)協(xié)議方法
- (NSInteger)add:(NSInteger)a b:(NSInteger)b
{
    return a+b;
}
//重寫setter方法方便打印信息,
- (void)setSum:(NSInteger)sum
{
    NSLog(@"--%@", @(sum));
    _sum = sum;
}

@end

//在VC中進(jìn)行測試
@interface ViewController () <JSExportTest>

@property (nonatomic, strong) JSProtocolObj *obj;
@property (nonatomic, strong) JSContext *context;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //創(chuàng)建context
    self.context = [[JSContext alloc] init];
    //設(shè)置異常處理
    self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        [JSContext currentContext].exception = exception;
        NSLog(@"exception:%@",exception);
    };
    //將obj添加到context中
    self.context[@"OCObj"] = self.obj;
    //JS里面調(diào)用Obj方法,并將結(jié)果賦值給Obj的sum屬性
    [self.context evaluateScript:@"OCObj.sum = OCObj.addB(2,3)"];
    
}

demo很簡單,還是定義了一個兩個數(shù)相加的方法,還有一個保存結(jié)果的變量。在JS中進(jìn)行調(diào)用這個對象的方法,并將結(jié)果賦值sum。唯一要注意的是OC的函數(shù)命名和JS函數(shù)命名規(guī)則問題。協(xié)議中定義的是add: b:,但是JS里面方法名字是addB(a,b)??梢酝ㄟ^JSExportAs這個宏轉(zhuǎn)換成JS的函數(shù)名字。

修改下代碼:

@protocol JSExportTest <JSExport>
//用宏轉(zhuǎn)換下,將JS函數(shù)名字指定為add;
JSExportAs(add, - (NSInteger)add:(NSInteger)a b:(NSInteger)b);

@property (nonatomic, assign) NSInteger sum;

@end

//調(diào)用
[self.context evaluateScript:@"OCObj.sum = OCObj.add(2,3)"];

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

self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        [JSContext currentContext].exception = exception;
        NSLog(@"exception:%@",exception);
    };

內(nèi)存管理

現(xiàn)在來說說內(nèi)存管理的注意點(diǎn),OC使用的ARC,JS使用的是垃圾回收機(jī)制,并且所有的引用是都強(qiáng)引用,不過JS的循環(huán)引用,垃圾回收會幫它們打破。JavaScriptCore里面提供的API,正常情況下,OC和JS對象之間內(nèi)存管理都無需我們?nèi)リP(guān)心。不過還是有幾個注意點(diǎn)需要我們?nèi)チ粢庀隆?/p>

1、不要在block里面直接使用context,或者使用外部的JSValue對象。

//錯誤代碼:
self.context[@"block"] = ^(){
     JSValue *value = [JSValue valueWithObject:@"aaa" inContext:self.context];
};

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

//一個比較隱蔽的
     JSValue *value = [JSValue valueWithObject:@"ssss" inContext:self.context];
    
    self.context[@"log"] = ^(){
        NSLog(@"%@",value);
    };

這個是block里面使用了外部的value,value對context和它管理的JS對象都是強(qiáng)引用。這個value被block所捕獲,這邊同樣也會內(nèi)存泄露,context是銷毀不掉的。

//正確的做法,str對象是JS那邊傳遞過來。
self.context[@"log"] = ^(NSString *str){
        NSLog(@"%@",str);
    };

2、OC對象不要用屬性直接保存JSValue對象,因?yàn)檫@樣太容易循環(huán)引用了。

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

//定義一個JSExport protocol
@protocol JSExportTest <JSExport>
//用來保存JS的對象
@property (nonatomic, strong) JSvalue *jsValue;

@end

//建一個對象去實(shí)現(xiàn)這個協(xié)議:

@interface JSProtocolObj : NSObject<JSExportTest>
@end

@implementation JSProtocolObj

@synthesize jsValue = _jsValue;

@end

//在VC中進(jìn)行測試
@interface ViewController () <JSExportTest>

@property (nonatomic, strong) JSProtocolObj *obj;
@property (nonatomic, strong) JSContext *context;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //創(chuàng)建context
    self.context = [[JSContext alloc] init];
    //設(shè)置異常處理
    self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        [JSContext currentContext].exception = exception;
        NSLog(@"exception:%@",exception);
    };
   //加載JS代碼到context中
   [self.context evaluateScript:
   @"function callback (){};
   
    function setObj(obj) {
    this.obj = obj;
    obj.jsValue=callback;
}"];
   //調(diào)用JS方法
   [self.context[@"setObj"] callWithArguments:@[self.obj]];  
}

上面的例子很簡單,調(diào)用JS方法,進(jìn)行賦值,JS對象保留了傳進(jìn)來的obj,最后,JS將自己的回調(diào)callback賦值給了obj,方便obj下次回調(diào)給JS;由于JS那邊保存了obj,而且obj這邊也保留了JS的回調(diào)。這樣就形成了循環(huán)引用。

怎么解決這個問題?我們只需要打破obj對JSValue對象的引用即可。當(dāng)然,不是我們OC中的weak。而是之前說的內(nèi)存管理輔助對象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創(chuàng)建JSManagedValue對象。通過JSVirtualMachine的方法- (void)addManagedReference:(id)object withOwner:(id)owner來建立這個弱引用關(guān)系。通過- (void)removeManagedReference:(id)object withOwner:(id)owner``` 來手動移除他們之間的聯(lián)系。

把剛剛的代碼改下:


//定義一個JSExport protocol
@protocol JSExportTest <JSExport>
//用來保存JS的對象
@property (nonatomic, strong) JSValue *jsValue;

@end

//建一個對象去實(shí)現(xiàn)這個協(xié)議:

@interface JSProtocolObj : NSObject<JSExportTest>
//添加一個JSManagedValue用來保存JSValue
@property (nonatomic, strong) JSManagedValue *managedValue;

@end

@implementation JSProtocolObj

@synthesize jsValue = _jsValue;
//重寫setter方法
- (void)setJsValue:(JSValue *)jsValue
{
    _managedValue = [JSManagedValue managedValueWithValue:jsValue];
    
    [[[JSContext currentContext] virtualMachine] addManagedReference:_managedValue 
    withOwner:self];
}

@end

//在VC中進(jìn)行測試
@interface ViewController () <JSExportTest>

@property (nonatomic, strong) JSProtocolObj *obj;
@property (nonatomic, strong) JSContext *context;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //創(chuàng)建context
    self.context = [[JSContext alloc] init];
    //設(shè)置異常處理
    self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        [JSContext currentContext].exception = exception;
        NSLog(@"exception:%@",exception);
    };
   //加載JS代碼到context中
   [self.context evaluateScript:
   @"function callback (){}; 
   
   function setObj(obj) {
   this.obj = obj;
   obj.jsValue=callback;
   }"];
   //調(diào)用JS方法
   [self.context[@"setObj"] callWithArguments:@[self.obj]];  
}

注:以上代碼只是為了突出用 JSManagedValue來保存 JSValue,所以重寫了 setter 方法。實(shí)際不會寫這么搓的姿勢。。。應(yīng)該根據(jù)回調(diào)方法傳進(jìn)來參數(shù),進(jìn)行保存 JSValue。

3、不要在不同的 JSVirtualMachine 之間進(jìn)行傳遞JS對象。

一個 JSVirtualMachine可以運(yùn)行多個context,由于都是在同一個堆內(nèi)存和同一個垃圾回收下,所以相互之間傳值是沒問題的。但是如果在不同的 JSVirtualMachine傳值,垃圾回收就不知道他們之間的關(guān)系了,可能會引起異常。

線程

JavaScriptCore 線程是安全的,每個context運(yùn)行的時候通過lock關(guān)聯(lián)的JSVirtualMachine。如果要進(jìn)行并發(fā)操作,可以創(chuàng)建多個JSVirtualMachine實(shí)例進(jìn)行操作。

與UIWebView的操作

通過上面的demo,應(yīng)該差不多了解OC如何和JS進(jìn)行通信。下面我們看看如何對 UIWebView 進(jìn)行操作,我們不再通過URL攔截,我們直接取 UIWebViewcontext,然后進(jìn)行對JS操作。

UIWebView的finish的回調(diào)中進(jìn)行獲取


- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
}

上面用了私有屬性,可能會被蘋果給拒了。這邊要注意的是每個頁面加載完都是一個新的context,但是都是同一個JSVirtualMachine。如果JS調(diào)用OC方法進(jìn)行操作UI的時候,請注意線程是不是主線程。

參考:

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文由我們團(tuán)隊(duì)的 糾結(jié)倫 童鞋撰寫。 寫在前面 本篇文章是對我一次組內(nèi)分享的整理,大部分圖片都是直接從keynot...
    知識小集閱讀 15,382評論 11 172
  • 注:本文copy自http://www.itdecent.cn/p/ac534f508fb0,純屬當(dāng)筆記使用。 概...
    BookKeeping閱讀 785評論 1 3
  • 寫在前面 本篇文章是對我一次組內(nèi)分享的整理,大部分圖片都是直接從keynote上截圖下來的,本來有很多炫酷動效的,...
    等開會閱讀 14,700評論 6 69
  • 本博客主要分以下幾個方面來介紹iOS中的JavaScriptCore JavaScriptCore簡介 JavaS...
    dullgrass閱讀 4,404評論 1 38
  • 一、說明 本篇文章呢主要聊一下JavaScriptCore的使用,為自己做個記錄。JavaScriptCore 是...
    常綠籮閱讀 926評論 0 1

友情鏈接更多精彩內(nèi)容