前面講完攔截URL的方式實現JS與OC互相調用,終於到JavaScriptCore了。它是從iOS7開始加入的,用 Objective-C 把 WebKit 的 JavaScript 引擎封裝了一下,提供了簡單快捷的方式與JavaScript交互。 關於JavaScriptCore的使用有兩篇很好的文章:javascript
NSHipster中文版的JavaScriptCorehtml
iOS7 新JavaScriptCore框架入門介紹java
看了上述兩篇文章,對JavaScriptCore應該已經基本瞭解了。我就簡要介紹一下,而後用代碼來實際操做了。先上最終實現的效果:ios
JavaScriptCore
是一個iOS 7 新添加的框架,使用前須要先導入JavaScriptCore.framework
。 而後咱們在JavaScriptCore.h
中能夠看到,該框架主要的類就只有五個:git
JSVirtualMachine
看名字直譯是JS 虛擬機,也就是說JavaScript是在一個虛擬的環境中執行,而JSVirtualMachine
爲其執行提供底層資源。github
翻譯這段描述:一個JSVirtualMachine
實例,表明一個獨立的JavaScript
對象空間,併爲其執行提供資源。它經過加鎖虛擬機,保證JSVirtualMachine
是線程安全的,若是要併發執行JavaScript
,那咱們必須建立多個獨立的JSVirtualMachine
實例,在不一樣的實例中執行JavaScript
。web
經過alloc/init
就能夠建立一個新的JSVirtualMachine
對象。可是咱們通常不用新建JSVirtualMachine
對象,由於建立JSContext時,若是咱們不提供一個特性的JSVirtualMachine
,內部會自動建立一個JSVirtualMachine
對象。安全
JSContext
是爲JavaScript的執行提供運行環境,全部的JavaScript的執行都必須在JSContext
環境中。JSContext
也管理JSVirtualMachine
中對象的生命週期。每個JSValue
對象都要強引用關聯一個JSContext
。當與某JSContext
對象關聯的全部JSValue
釋放後,JSContext
也會被釋放。 建立一個JSContext對象的方式有:bash
// 1.這種方式須要傳入一個JSVirtualMachine對象,若是傳nil,會致使應用崩潰的。
JSVirtualMachine *JSVM = [[JSVirtualMachine alloc] init];
JSContext *JSCtx = [[JSContext alloc] initWithVirtualMachine:JSVM];
// 2.這種方式,內部會自動建立一個JSVirtualMachine對象,能夠經過JSCtx.virtualMachine
// 看其是否建立了一個JSVirtualMachine對象。
JSContext *JSCtx = [[JSContext alloc] init];
// 3. 經過webView的獲取JSContext。
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
複製代碼
上面推薦的兩篇文章以及網上介紹JavaScriptCore的文章可能是經過1和2這兩種方式建立JSContext,而後執行JavaScript,演示JavaScriptCore。我一直有疑問,若是不是HTML結合OC,纔會使用到JavaScript,那在一個虛擬的環境裏運行JS有什麼意義。 因此,後面我是用方式3來建立JSContext。併發
JSValue
都是經過JSContext
返回或者建立的,並無構造方法。JSValue
包含了每個JavaScript類型的值,經過JSValue
能夠將Objective-C中的類型轉換爲JavaScript中的類型,也能夠將JavaScript中的類型轉換爲Objective-C中的類型。 上述兩篇文章中均有OC、JSValue、JavaScript的類型對應關係表。
JSManagedValue
主要用途是解決JSValue對象在Objective-C 堆上的安全引用問題。把JSValue 保存進Objective-C 堆對象中是不正確的,這很容易引起循環引用,而致使JSContext不能釋放。 這個類主要是將JSValue對象轉換爲JSManagedValue的API,並且也不經常使用,就不作具體介紹了。之後遇到使用場景再補充。
JSExport
是一個協議類,可是該協議並無任何屬性和方法。 怎麼使用呢? 咱們能夠自定義一個協議類,繼承自JSExport
。不管咱們在JSExport
裏聲明的屬性,實例方法仍是類方法,繼承的協議都會自動的提供給任何 JavaScript 代碼。 So,咱們只須要在自定義的協議類中,添加上屬性和方法就能夠了。
由於該系列主要是JS與OC互調,因此主要介紹如何用JavaScriptCore實現JS與OC互調。
這步跟 文章(一)中的步驟一是同樣的。
self.webView = [[UIWebView alloc] initWithFrame:self.view.frame];
self.webView.delegate = self;
NSURL *htmlURL = [[NSBundle mainBundle] URLForResource:@"index.html" withExtension:nil];
// NSURL *htmlURL = [NSURL URLWithString:@"http://www.baidu.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:htmlURL];
// 若是不想要webView 的回彈效果
self.webView.scrollView.bounces = NO;
// UIWebView 滾動的比較慢,這裏設置爲正常速度
self.webView.scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
[self.webView loadRequest:request];
[self.view addSubview:self.webView];
複製代碼
HTML的內容也大體同樣,不過JS的調用有些區別,更簡單了。
function shareClick() {
share('測試分享的標題','測試分享的內容','url=http://www.baidu.com');
}
function shareResult(channel_id,share_channel,share_url) {
var content = channel_id+","+share_channel+","+share_url;
asyncAlert(content);
document.getElementById("returnValue").value = content;
}
function locationClick() {
getLocation();
}
function setLocation(location) {
asyncAlert(location);
document.getElementById("returnValue").value = location;
}
複製代碼
更詳細的能夠看demo中的HTML源碼,demo地址在文章末。
在HMTL加載成功的回調方法- (void)webViewDidFinishLoad:(UIWebView *)webView
中添加要調用的原生OC方法。
#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSLog(@"webViewDidFinishLoad");
[self addCustomActions];
}
複製代碼
將全部要添加的功能方法,集中到一個方法addCustomActions
中,便於維護。
#pragma mark - private method
- (void)addCustomActions
{
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
[self addScanWithContext:context];
[self addLocationWithContext:context];
[self addSetBGColorWithContext:context];
[self addShareWithContext:context];
[self addPayActionWithContext:context];
[self addShakeActionWithContext:context];
[self addGoBackWithContext:context];
}
複製代碼
而後每個小功能獨立開來,這樣修改和解決Bug的時候可以快速定位到某個功能。
- (void)addShareWithContext:(JSContext *)context
{
__weak typeof(self) weakSelf = self;
context[@"share"] = ^() {
NSArray *args = [JSContext currentArguments];
if (args.count < 3) {
return ;
}
NSString *title = [args[0] toString];
NSString *content = [args[1] toString];
NSString *url = [args[2] toString];
// 在這裏執行分享的操做...
// 將分享結果返回給js
NSString *jsStr = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];
[[JSContext currentContext] evaluateScript:jsStr];
};
}
複製代碼
注意:
[JSContext currentContext]
。固然咱們能夠將JSContext 和JSValue當作block的參數傳進去,這樣就可使用啦。OC調用JS方法就有多種方式了。首先介紹使用JavaScriptCore框架的方式。 ** 方式1 ** 使用JSContext的方法-evaluateScript
,能夠實現OC調用JS方法。 下面是一個調用JS中payResult
方法的示例代碼:
NSString *jsStr = [NSString stringWithFormat:@"payResult('%@')",@"支付成功"];
[[JSContext currentContext] evaluateScript:jsStr];
複製代碼
** 方式2 ** 使用JSValue的方法-callWithArguments
,也能夠實現OC調用JS方法。 下面這個示例代碼依然是調用JS中的payResult
:
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
[context[@"payResult"] callWithArguments:@[@"支付彈窗"]];
複製代碼
固然,若是是在執行原生OC方法以後,想要在OC執行完操做後,將結果回調給JS時,能夠這樣寫:
- (void)addPayActionWithContext:(JSContext *)context
{
context[@"payAction"] = ^() {
NSArray *args = [JSContext currentArguments];
if (args.count < 4) {
return ;
}
NSString *orderNo = [args[0] toString];
NSString *channel = [args[1] toString];
long long amount = [[args[2] toNumber] longLongValue];
NSString *subject = [args[3] toString];
// 支付操做
NSLog(@"orderNo:%@---channel:%@---amount:%lld---subject:%@",orderNo,channel,amount,subject);
// 將支付結果返回給js
[[JSContext currentContext][@"payResult"] callWithArguments:@[@"支付成功"]];
};
}
複製代碼
之前介紹過的,利用UIWebView的API。
NSString *jsStr = [NSString stringWithFormat:@"payResult('%@')",@"支付成功"];
[weakSelf.webView stringByEvaluatingJavaScriptFromString:jsStr];
複製代碼
好處:使用JavaScriptCore,JS調用Native方法時,參數的傳遞更方便,不用擔憂特殊符號的轉換問題。 很差的地方:只能使用在iOS 7以上。這點我相信如今基本沒有多少應用還兼容iOS 6了吧,我去年在作這個功能的時候,還要兼容iOS 6 😭 😭 。
先把JS與OC互調部分的介紹完了,這裏再補充一些關於JavaScriptCore的相關知識。 在OC中如何往JS環境中添加一個變量,便於後續在JS中使用呢?
JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"var arr = [3, 4, 'abc'];"];
複製代碼
而用到實際的UIWebView上,能夠這樣:
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
[context evaluateScript:@"var arr = [3, 4, 'abc'];"];
複製代碼
當上面這兩行代碼執行完後,我點擊HTML中的按鈕
<input type="button" value="輸出arr" onclick="showArr()" />
function showArr(){
asyncAlert(arr);
}
function asyncAlert(content) {
setTimeout(function(){
alert(content);
},1);
}
複製代碼
直接輸出arr,結果是這樣的:
若是咱們在OC中想要取出arr,只須要這樣:
JSValue *value = context[@"arr"];
複製代碼
OC中的block能夠傳入到JavaScript中,這樣就建立了一個新的JS方法。咱們上面的JS調用OC方法,就是利用的這個實現的。
關於JSExport如何使用? JSExport 主要是用於將OC中定義的Model類等引入到JavaScript中,便於在JS中使用這種對象和對象的屬性、方法。
JSExport的大體使用流程是:
1.建立一個自定義協議
XXXExport
繼承自JSExport
。
2.在自定義的
XXXExport
中添加JS裏須要調用的屬性和方法。
3.在自定義的Model類中實現XXXExport中的屬性的get/set方法以及定義的方法。
4.經過JSContext將Model類或者Model類的實例插入到JavaScript中。
固然,咱們也能夠給已經存在的類動態添加協議,來使其能夠供JS 使用。這些示例和示例代碼,在文章NSHipster中文版的JavaScriptCore 和 JavaScriptCore框架在iOS7中的對象交互和管理中有很詳細的介紹和使用展現。
關於WKWebView 與JavaScriptCore,因爲WKWebView 不支持經過以下的KVC的方式建立JSContext:
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
複製代碼
那麼就不能在WKWebView中使用JavaScriptCore了。 並且,WKWebView中有OC 和JS交互的方式,更easy 、更簡潔,所以也用不着使用JavaScriptCore。 WKWebView中如何實現OC與JS交互能夠看前面這篇文章:iOS下JS與OC互相調用(三)--MessageHandler
UIWebView利用JavaScriptCore來實現交互的示例工程:JS_OC_JavaScriptCore
Have Fun!