iOS7之后,蘋果推出了JavaScriptCore,是WebKit的一個重要組成部分,對JS進行解析和提供運行環境。
JSContext
JS上下文,提供運行環境,同時也通過JSVirtualMachine管理著所有對象的生命周期,每個JSValue都和JSContext相關聯并且強引用context。
JSValue
JS和OC互換的橋梁,它提供了多種方法實現OC與JS之間的互換。
JSManagedValue
JS和OC對象的內存管理輔助對象。由于JS內存管理是垃圾回收,并且JS中的對象都是強引用,而OC是引用計數。如果雙方相互引用,勢必會造成循環引用,而導致內存泄露。我們可以用JSManagedValue保存JSValue來避免。
JSVirtualMachine
JS運行的虛擬機,有獨立的堆空間和垃圾回收機制。- (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;,如果是使用- (id)init;進行初始化,那么在其內部會自動創建一個新的JSVirtualMachine對象然后調用前邊的初始化方法。
JSExport
一個協議,如果JS對象想直接調用OC對象里面的方法和屬性,那么這個OC對象只要實現這個JSExport協議就可以了。
OC中調用JS
從JS環境里取函數或者變量,就是將一段JS代碼通過JSContext轉化為OC執行。
#pragma mark - 加載一段JS代碼
-(void)OCCallJS
{
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NSString *jsString = [NSString stringWithFormat:@"alert('%@')",@"這是OC調用JS"];
[context evaluateScript:jsString];
}
當JS代碼多,直接食用字符串轉化可能會出現錯誤,此時可以讀取本地.html/.js文件來實現
#pragma mark - 加載本地.js文件
-(void)loadScriptWithContext:(JSContext *)context AndFileName:(NSString *)fileName
{
NSString *filePath = [NSString stringWithFormat:@"%@/JS/%@",[[NSBundle mainBundle] resourcePath],fileName];
NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
[context evaluateScript:script];
}
//加載.html文件
NSURL *htmlURL = [[NSBundle mainBundle]URLForResource:@"js.html" withExtension:nil];
NSURLRequest *request = [NSURLRequest requestWithURL:htmlURL];
[self.webView loadRequest:request];
JS中調用OC
方法一:將block注入JSContext上下文,在JS執行的時候調用OC原生方法
方法二:將OC對象注入JSContext上下文,在JS執行的時候調用OC原生方法
一般寫在UIWebView的delegate中
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//JS調OC方法一
//首先創建JSContext 對象(此處通過當前webView的鍵獲取到jscontext)
JSContext *context1 = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
context1[@"JSCallOCWithBlock"] = ^(NSString *str){
NSLog(@"OC的Block被調用:%@",str);
};
//JS調OC方法二
JSContext *context2 = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
GemJSObject *jsObject = [[GemJSObject alloc]init]; //創造一個對象
jsObject.x = 10;
context2[@"JSCallOCWithObject"] = jsObject; //將對象注入JS運行環境
}
OC對象中的代碼
#pragma mark - GemJSObject.h
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol GemJSObjectExport <JSExport>
//定義要暴露給JS的屬性和方法,JS就可以對其操作了
@property int x;
-(void)nslog:(NSString *)str;
@end
@interface GemJSObject : NSObject<GemJSObjectExport>
-(void)nslog:(NSString *)str;
@end
#pragma mark - GemJSObject.m
#import "GemJSObject.h"
@implementation GemJSObject
@synthesize x;
-(void)nslog:(NSString *)str
{
NSLog(@"%d",self.x * self.x);
NSLog(@"OC的nslog方法被調用:%@",str);
}
@end
js.html文件中的JS代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>test javascript</title>
</head>
<body>
<div>
<!--
調用方法(并傳入參數,若block中無參,則此參數無用)
-->
<button onclick="JSCallOCWithBlock('JS調用OC方法,并傳參(通過block)');">JS_OC_Block</button>
<br>
<!--
調用對象的nslog方法(并傳入參數),對象需要傳入自定義的NSObject對象(遵循JSExport協議)
-->
<button onclick="JSCallOCWithObject.nslog('JS調用OC方法,并傳參(通過GemJSObject對象)');">JS_OC_Object</button>
</div>
</body>
</html>
方法三:攔截非正常的URL
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
//JS調OC方法三:攔截不正常URL
if ([request.URL.absoluteString isEqualToString:@"非正常的URL頭"]) {
//調用OC的方法
[self handleCustomAction:request.URL];
return NO;
}
return YES;
}
-(void)handleCustomAction:(NSURL *)url
{
if ([url.host isEqualToString:@"getLocation"]) {
[self getLocation];
}
}
-(void)getLocation
{
//獲取位置信息,將結果返回給js
NSString *jsStr = [NSString stringWithFormat:@"setLocation('%@')",@"北京市朝陽區XXX號"];
[self.webView stringByEvaluatingJavaScriptFromString:jsStr];
/**
補充:在JS代碼中調OC,也需要傳參數到OC中,就像一個get請求一樣,把參數放在后面。
function shareClick() {
loadURL("haleyAction://shareClick?title=測試分享的標題&content=測試分享的內容&url=http://www.baidu.com");
}
*/
}
實際應用中,可能會遇見瀏覽webView圖片的情況,我的GitHub有相關demo可參考瀏覽webView圖片demo