就目前市面上的App来说很多都是原生+ H5的作为基底开发的,因为H5在页面交互的处理以及动画效果存在很大的优势,所以现在开发的App原生界面里面掺杂着H5页面是很常见的。
最近在开发的App也是,使用谷歌地图,在iOS端原生的需要付费才能提供更多的支持,而在Web端api都是开放使用的,而且就于开发而言Web端更为简单。
大量的趋势造就了需求,在技术群很多人都在问原生和H5怎么用来交互,我就趁着最近的工作给大家写一篇相关的开发文档,简单解释一下JavaScriptCore的使用。
1.什么是JavaScriptCore?
JavaScriptCore是苹果Safari浏览器的JavaScript引擎,是基于webkit中以C/C++实现的JavaScriptCore的一个包装,在旧版本iOS开发中,很多开发者也会自行将webkit的库引入项目编译使用。不过在iOS7之后苹果就把它做成了一个标准库,提供开发者使用
2.JavaScriptCore中的类
在项目中引入JavaScriptCore后,链到头文件中,除了大段的Copyright注释可以看到里面只要引入了5个文件,每个文件里都定义跟文件名对应的类:
· JSContext
· JSValue
· JSManagedValue
· JSVirtualMachine
· JSExport
3.JSContext和JSValue
JSVirtualMachine为JavaScript的运行提供了底层资源,JSContext就为其提供着运行环境,通过- (JSValue *)evaluateScript:(NSString *)script;方法就可以执行一段JavaScript脚本,并且如果其中有方法、变量等信息都会被存储在其中以便在需要的时候使用。而JSContext的创建都是基于JSVirtualMachine:-
(id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;,如果是使用- (id)init;进行初始化,那么在其内部会自动创建一个新的JSVirtualMachine对象然后调用前边的初始化方法。
JSValue则可以说是JavaScript和Object-C之间互换的桥梁,它提供了多种方法可以方便地把JavaScript数据类型转换成Objective-C,或者是转换过去。
4.Objective-C调用JavaScript
oc想要调用js代码的话,先创建一个JSContext对象实例,接着通过evaluateScript加载js代码到context对象中,然后获取js对象,如果为js函数对象,通过callWithArguments调用该js函数,并且可以以数组的方式传递参数。
//js代码段
var appendString = function(name) {
return 'string:' + name;
};
var arr = [1, 2 , 'hello world'];
//m文件
NSString *jsPath = [[NSBundle mainBundle] pathForResource:@"test"ofType:@"js"];
NSString *jsContent = [NSString stringWithContentsOfFile:jsPath encoding:NSUTF8StringEncoding error:nil];
JSContext *context = [[JSContext alloc] init];
[context evaluateScript:jsContent];
JSValue *value = [context[@"appendString"] callWithArguments:@[@"hello"]];
JSValue *value1 = context[@"arr"];
NSLog(@"appendString:%@",[value toString] );//appendString:string:hello
NSLog(@"arr:%@",[value1 toArray] );
5.JavaScript调用Objective-C
(1)Block方式
我们可以通过block的方式将oc代码暴露给js,JavaScriptCore会自动将oc block包装在js函数中,我们就可以直接在js中调用该block函数
JSContext *context = [[JSContext alloc] init];
context[@"sayhi"] = ^(NSString *name) {
NSLog(@"say hi to %@",name);
};
[context evaluateScript:@"sayhi('Greg')"];
(2)JSExport协议如果你到头文件中去查看JSExport协议,你会发现这个协议其实没有定义任何东西。JavaScriptCore提供这个协议用来将oc的方法跟属性暴露给js调用,其中@property会转换成js的getter和setter方法,实例方法会转换成js函数,而类方法则转换成js中global object的方法。
//定义需要暴露给js的内容,这里我们只暴露personName和queryPersonName接口
@protocol PersonProtocol <JSExport>
@property(nonatomic,copy)NSString *personName;
-(NSString *)queryPersonName;
@end
//Person实现PersonProtocol协议,而自己定义的age和queryPersonAge接口不暴露给js
@interface Person : NSObject <PersonProtocol>
@property(nonatomic,assign)NSInteger age;
-(NSInteger)queryPersonAge;
@end
@implementation Person
@synthesize personName = _personName;
-(NSString *)queryPersonName{
return self.personName;
}
-(NSInteger)queryPersonAge{
return self.age;
}
@end
JSContext *context = [[JSContext alloc] init];
//创建Person类的对象,将他赋值给js对象
Person *person=[Person new];
person.personName = @"Greg";
person.age = 27;
context[@"person"]=person;
//可以调用获取PersonProtocol暴露的内容
NSString *personName = [[context evaluateScript:@"person.personName"] toString];
NSString *personName1 = [[context evaluateScript:@"person.queryPersonName()"] toString];
//js无法调用跟age相关的内容
NSInteger age = [[context evaluateScript:@"person.age"] toInt32];
NSInteger age1 = [[context evaluateScript:@"person.queryPersonAge()"] toInt32];
6.JavaScriptCore在Web容器中的使用
在UIWebView中,我们可以在- (void)webViewDidFinishLoad:(UIWebView *)webView方法中,通过KVC的方式获取到当前容器的JSContent对象,通过该对象,我们就可以方便的进行hybrid操作。
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
功能例子:在html中调研OC代码中的分享功能和调用相机功能。
(1)HelloWord.html代码如下:
function jsCallNative(){
WBBridge.callCamera();
}
function jsCallNative2(){
var shareInfo = "分享内容";
var str = WBBridge.share(shareInfo);
alert(str);
}<input type="button" onclick="jscallnative()" value="jscallnative" ><input type="button" onclick="jscallnative2()" value="jscallnative2" >
</input type="button" onclick="jscallnative2()" value="jscallnative2" ></input type="button" onclick="jscallnative()" value="jscallnative" >
(2)实现一个遵守JSExport的协议WebViewJSExport
@protocol WebViewJSExport - (void)callCamera;
- (NSString*)share:(NSString*)shareString;
@end
(3)当前VC需要实现WebViewJSExport
@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];
NSURL *url = [[NSURL alloc] initWithString:@"http://localhost:8080/myDiary/HelloWorld.html"];
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>
(1)HelloWord.html代码如下:
function jsCallNative(){
WBBridge.callCamera();
}
function jsCallNative2(){
var shareInfo = "分享内容";
var str = WBBridge.share(shareInfo);
alert(str);
}<input type="button" onclick="jscallnative()" value="jscallnative" ><input type="button" onclick="jscallnative2()" value="jscallnative2" >
</input type="button" onclick="jscallnative2()" value="jscallnative2" ></input type="button" onclick="jscallnative()" value="jscallnative" >
(2)实现一个遵守JSExport的协议WebViewJSExport
@protocol WebViewJSExport - (void)callCamera;
- (NSString*)share:(NSString*)shareString;
@end
(3)当前VC需要实现WebViewJSExport
@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];
NSURL *url = [[NSURL alloc] initWithString:@"http://localhost:8080/myDiary/HelloWorld.html"];
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>