一、簡單介紹
JSCore全稱為JavaScriptCore,是蘋果公司在iOS中加入的一個新的framework。該framework為OC與JS代碼相互操作的提供了極大的便利。該工程默認是沒有導入工程中的,需要我們手動添加。

添加完成后,我們可以看到JavaScriptCore.h中包含以下5個主要的文件。
1
2
3
4
5
|
#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"
|
JSContext: 代表JavaScript的執行環境。你可以創建JSContent在OC環境中執行JavaScript腳本,同時也可以在JavaScript腳本中訪問OC中的值或者方法。
JSValue:是OC和JavaScript值相互轉化的橋梁。他提供了很多方法把OC和JavaScript的數據類型進行相互轉化。其一一對應關系如下表所示:
JSManagedValue:JSValue的包裝類。JS和OC對象的內存管理輔助對象。由於JS內存管理是垃圾回收,並且JS中的對象都是強引用,而OC是引用計數。如果雙方相互引用,勢必會造成循環引用,而導致內存泄露。我們可以用JSManagedValue保存JSValue來避免。
JSVirtualMachine: JS運行的虛擬機。可以支持並行的JavaScript執行,管理JavaScript和OC轉換中的內存管理。
JSExport:一個協議,如果JS對象想直接調用OC對象里面的方法和屬性,那么這個OC對象只要實現這個JSExport協議就可以了。
下面我們通過實例案例來學習JSCore的用法。
二、OC中調用JS方法
案例一:我在js中定義了一個函數add(a,b),我們需要在OC中進行調用。
1
2
3
4
5
6
7
8
9
10
11
|
-(void)OCCallJS{
self.context = [[JSContext alloc] init];
NSString *js = @
"function add(a,b) {return a+b}"
;
[self.context evaluateScript:js];
JSValue *addJS = self.context[@
"add"
];
JSValue *sum = [addJS callWithArguments:@[@(10),@(17)]];
NSInteger intSum = [sum toInt32];
NSLog(@
"intSum: %zi"
,intSum);
}
|
三、JS中調用OC方法
JS中調用OC有兩種方法,第一種為block調用,第二種為JSExport protocol。
案例二:我們在OC中定義了一個如下方法,我們需要在JS中對它進行調用
1
2
3
|
-(NSInteger)add:(NSInteger)a and:(NSInteger)b{
return
a+b;
}
|
3.1、block調用
1
2
3
4
5
6
7
8
9
10
11
|
-(void)JSCallOC_block{
self.context = [[JSContext alloc] init];
__weak
typeof
(self) weakSelf = self;
self.context[@
"add"
] = ^NSInteger(NSInteger a, NSInteger b){
return
[weakSelf add:a and:b];
};
JSValue *sum = [self.context evaluateScript:@
"add(4,5)"
];
NSInteger intSum = [sum toInt32];
NSLog(@
"intSum: %zi"
,intSum);
}
|
3.2、JSExport protocol
第一步:定義一個遵守JSExport的AddJSExport協議。
1
2
3
4
5
|
@protocol AddJSExport <jsexport>
//用宏轉換下,將JS函數名字指定為add;
JSExportAs(add, - (NSInteger)add:(NSInteger)a and:(NSInteger)b);
@property (nonatomic, assign) NSInteger sum;
@end</jsexport>
|
第二步:新建一個對象AddJSExportObj,去實現以上協議。
1
2
3
4
5
6
7
8
9
10
|
AddJSExportObj.h
@interface AddJSExportObj : NSObject<addjsexport>
@property (nonatomic, assign) NSInteger sum;
@end
AddJSExportObj.m
@implementation AddJSExportObj
-(NSInteger)add:(NSInteger)a and:(NSInteger)b{
return
a+b;
}
@end</addjsexport>
|
第三步:在VC中進行JS調用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
-(void)JSCallOC_JSExport{
self.context = [[JSContext alloc] init];
//異常處理
self.context.exceptionHandler = ^(JSContext *context, JSValue *exception){
[JSContext currentContext].exception = exception;
NSLog(@
"exception:%@"
,exception);
};
self.addObj = [[AddJSExportObj alloc] init];
self.context[@
"OCAddObj"
] = self.addObj;
//js中的OCAddObj對象==>OC中的AddJSExportObj對象
[self.context evaluateScript:@
"OCAddObj.sum = OCAddObj.add(2,30)"
];
NSLog(@
"%zi"
,self.addObj.sum);
}
|
四、一個從服務端下發JS腳本,執行本地方法的實現思路
案例三:本地定義了一系列方法,可以通過服務端下發js腳本去控制具體去執行那些方法。這樣就可以在遠端實現對於客戶端的控制。
第一步:預置本地方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
-(void)initJS{
__weak
typeof
(self) weakSelf = self;
self.context[@
"execute1"
] = ^(){
[weakSelf execute1];
};
self.context[@
"execute2"
] = ^(){
[weakSelf execute2];
};
}
-(void)execute1{
NSLog(@
"execute1"
);
}
-(void)execute2{
NSLog(@
"execute2"
);
}
|
第二步:服務端下發腳本
1
2
3
4
5
|
-(NSString *)getJS{
//可以從服務端下發
//return @"execute1()";
return
@
"execute2()"
;
}
|
第三步:根據服務端下發腳本執行
1
2
3
4
5
|
-(void)executeByJs{
[self initJS];
NSString *js = [self getJS];
[self.context evaluateScript:js];
}
|
五、JSCore在Web容器中的使用
在UIWebView中,我們可以在- (void)webViewDidFinishLoad:(UIWebView *)webView
方法中,通過KVC的方式獲取到當前容器的JSContent對象,通過該對象,我們就可以方便的進行hybrid操作。
1
|
JSContext *context = [self.webView valueForKeyPath:@
"documentView.webView.mainFrame.javaScriptContext"
];
|
案例演示:在html中調研OC代碼中的分享功能和調用相機功能。
第一步:HelloWord.html代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function
jsCallNative(){
WBBridge.callCamera();
}
function
jsCallNative2(){
var
shareInfo =
"分享內容"
;
var
str = WBBridge.share(shareInfo);
alert(str);
}
<input type=
"button"
onclick=
"jscallnative()"
value=
"jscallnative"
>
<br>
<input type=
"button"
onclick=
"jscallnative2()"
value=
"jscallnative2"
>
<br></input type=
"button"
onclick=
"jscallnative2()"
value=
"jscallnative2"
></input type=
"button"
onclick=
"jscallnative()"
value=
"jscallnative"
>
|
第二步:實現一個遵守JSExport的協議WebViewJSExport
1
2
3
4
|
@protocol WebViewJSExport <jsexport>
- (void)callCamera;
- (NSString*)share:(NSString*)shareString;
@end</jsexport>
|
第三步:當前VC需要實現WebViewJSExport
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
@interface ViewController ()<uiwebviewdelegate,webviewjsexport>
@property (nonatomic, strong) JSContext *context;
@property (nonatomic, strong) UIWebView *webView;
@end
@implementation ViewController
-(void)initWebView{
self.context = [[JSContext alloc] init];
_webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
_webView.delegate = self;
[self.view addSubview:_webView];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView{
JSContext *context = [self.webView valueForKeyPath:@
"documentView.webView.mainFrame.javaScriptContext"
];
_context = context;
// 將本對象與 JS 中的 WBBridge 對象橋接在一起,在 JS 中 WBBridge 代表本對象
[_context setObject:self forKeyedSubscript:@
"WBBridge"
];
_context.exceptionHandler = ^(JSContext* context, JSValue* exceptionValue) {
context.exception = exceptionValue;
NSLog(@
"異常信息:%@"
, exceptionValue);
};
}
- (void)callCamera{
NSLog(@
"調用相機"
);
}
- (NSString*)share:(NSString*)shareString{
NSLog(@
"分享::::%@"
,shareString);
return
@
"分享成功"
;
}
@end</uiwebviewdelegate,webviewjsexport>
|
這樣我們就可以在webView中調用我們native組建了,實現了一個簡單的hybird功能。這就補充了在UIWebView實現hybird功能的方式。還有一種方式就是在iOS H5容器的一些探究(一):UIWebView和WKWebView的比較和選擇一文中見過的加載隱藏iframe,來攔截請求的方式。
補充
對於WKWebView,目前還沒有能夠拿到JSContent的對象的方式。
六、參考資料
文/景銘巴巴(簡書作者)
著作權歸作者所有,轉載請聯系作者獲得授權,並標注“簡書作者”。