1、简要介绍JavaScriptCore
JavaScriptCore
是一个iOS 7 新添加的框架,使用前需要先导入JavaScriptCore.framework
。
然后我们在JavaScriptCore.h
中可以看到,该框架主要的类就只有五个:
1.1 JSVirtualMachine JSVirtualMachine
看名字直译是JS 虚拟机,也就是说JavaScript是在一个虚拟的环境中执行,而JSVirtualMachine
为其执行提供底层资源。
1.2 JSContext
JSContext是为JavaScript的执行提供运行环境,所有的JavaScript的执行都必须在JSContext环境中。JSContext也管理JSVirtualMachine中对象的生命周期。每一个JSValue对象都要强引用关联一个JSContext。当与某JSContext对象关联的所有JSValue释放后,JSContext也会被释放。
通常情况下我们一般都这样创建JSContext:
// 通过webView的获取JSContext。
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
1.3 JSValue
JSValue都是通过JSContext返回或者创建的,并没有构造方法。JSValue包含了每一个JavaScript类型的值,通过JSValue可以将Objective-C中的类型转换为JavaScript中的类型,也可以将JavaScript中的类型转换为Objective-C中的类型。
OC、JSValue、JavaScript的类型对应关系表:
1.4 JSManagedValue
JSManagedValue主要用途是解决JSValue对象在Objective-C 堆上的安全引用问题。把JSValue 保存进Objective-C 堆对象中是不正确的,这很容易引发循环引用,而导致JSContext不能释放。 这个类主要是将JSValue对象转换为JSManagedValue的API,而且也不常用,就不做具体介绍了。
1.5 JSExport
JSExport是一个协议类,但是该协议并没有任何属性和方法。 怎么使用呢? 我们可以自定义一个协议类,继承自JSExport。无论我们在JSExport里声明的属性,实例方法还是类方法,继承的协议都会自动的提供给任何 JavaScript 代码。 So,我们只需要在自定义的协议类中,添加上属性和方法就可以了。
JS调用OC分两种情况
一,js里面直接调用方法(Block方式)
二,js里面通过对象调用方法(JSExport协议方式)
先说第一种吧:
创建UIWebView:
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;
}
添加JS要调用的原生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 ;
}
// 这个地方取到的值是jsvalue,需要转化一下
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];
};
}
注意: 1.JS要调用的原生OC方法,可以在viewDidLoad webView被创建后就添加好,但最好是在网址加载成功后再添加,以避免无法预料的乱入Bug。
2.block 中的执行环境是在子线程中。奇怪的是竟然可以更新部分UI,例如给view设置背景色,调用webView执行js等,但是弹出原生alertView就会在控制台报子线程操作UI的错误信息。
3.避免循环引用,因为block 会持有外部变量,而JSContext也会强引用它所有的变量,因此在block中调用self时,要用__weak 转一下。而且在block内不要使用外部的context 以及JSValue,都会导致循环引用。如果要使用context 可以使用[JSContext currentContext]。当然我们可以将JSContext 和JSValue当做block的参数传进去,这样就可以使用啦。
OC调用JS方法:
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
// NSString *jsStr = [NSString stringWithFormat:@"payResult('%@')",@"支付成功"];
// [[JSContext currentContext] evaluateScript:jsStr];
//自动回调
[[JSContext currentContext][@"payResult"] callWithArguments:@[@"支付成功"]];
};
}
方式3 利用UIWebView的API:
NSString *jsStr = [NSString stringWithFormat:@"payResult('%@')",@"支付成功"];
[self.webView stringByEvaluatingJavaScriptFromString:jsStr];
第一种方式到此介绍完毕.
下面介绍第二种<JSExport>协议的方式:
首先我们新建一个类,JSNativeMethod:
.h 声明
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
//首先创建一个实现了JSExport协议的协议
@protocol TestJSObjectProtocol <JSExport>
//此处我们测试几种参数的情况
- (NSString *)imgCallBack:(NSString *)url;
// 通过JSON传过来
- (void)callWithDict:(NSDictionary *)params;
@end
@interface JSNativeMethod : NSObject<TestJSObjectProtocol>
@end
.m实现
@implementation JSNativeMethod
- (NSString *)imgCallBack:(NSString *)url {
NSLog(@"touch image %@",url);
return @"iOS To H5";
}
- (void)callWithDict:(NSDictionary *)params {
NSLog(@"%@",params);
JSValue *jsFunc = self.jsContext[@"uploadimage"];
[jsFunc callWithArguments:@[@{@"image":@"image upload success"}]];
}
@end
然后在WebView代理方法里:
#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
// 获取当前JS运行环境
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
/ JSNativeMethod *call = [[JSNativeMethod alloc] init];
//将JSNativeMethod封装到JavaScript函数Native()中
self.jsContext[@"Native"] = call;
//JSContext 还有另外一个有用的招数:通过设置上下文的 exceptionHandler 属性,你可以观察和记录语法,类型以及运行时错误。 exceptionHandler 是一个接收一个 JSContext 引用和异常本身的回调处理
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
NSLog(@"异常信息:%@", exceptionValue);
};
}
利用这种方式:需要js那边同样需要Native:
//app端callback callWithDict()
function callWithDict(a) {
log(JSON.stringify(a));
window.Native.callWithDict(a);
}
OK,基本结束,这种方式demo还没整理好,只是在我之前项目做了部分修改,因涉及源码,demo暂时不上,以后补上.
第一种方式:demo地址(Block形式):https://github.com/domanc/JS_OC_JavaScriptCore.git
第二种方式: 待更新!