从应用程序内部评估JavaScript(JavaScript(JS)是一种基于对象的脚本语言,它不仅可以创建对象,也能使用现有的对象。JavaScript 是属于 HTML 和 Web 的编程语言。)程序,并支持应用程序的JavaScript脚本。
概述
JavaScriptCore框架提供了从Swift、Objective-C和基于c的应用程序中评估JavaScript程序的能力。您还可以使用JavaScriptCore向JavaScript环境插入定制对象。
类
1.JSContext JSContext对象表示JavaScript执行环境。您创建并使用JavaScript上下文来评估来自Objective-C或Swift代码的JavaScript脚本,访问在JavaScript中定义或计算的值,以及使JavaScript可以访问本机对象、方法或函数。
2.JSManagedValue JSManagedValue对象封装JSValue对象,添加“条件保留”行为来提供值的自动内存管理。托管值的主要用例是将JavaScript值存储在Objective-C或Swift对象中,该对象本身被导出到JavaScript。
3.JSValue JSValue实例是对JavaScript值的引用。您可以使用JSValue类在JavaScript和Objective-C或Swift表示之间转换基本值(比如数字和字符串),以便在本机代码和JavaScript代码之间传递数据。您还可以使用这个类来创建JavaScript对象,这些对象包装自定义类或JavaScript函数的本机对象,其实现由本机方法或块提供。
4.JSVirtualMachine JSVirtualMachine实例表示用于JavaScript执行的自包含环境。使用该类有两个主要目的:支持并发JavaScript执行,以及管理连接在JavaScript和Objective-C或Swift之间的对象的内存。
协议-Protocols---JSExport 将Objective-C类及其实例方法、类方法和属性导出到JavaScript代码的协议。
JSBase.h 定义JavaScriptCore接口引擎。
JSContextRef.h JSObjectRef.h 一个JavaScript对象。
JSStringRef.h 一个UTF16字符缓冲区,它是JavaScript中的基本字符串表示。
JSStringRefCF.h 包含CFString便利方法。
JSValueRef.h 一个JavaScript值,它是所有JavaScript值及其上的多态函数的基类型。
JSContext
SContext对象表示JavaScript执行环境。您创建并使用JavaScript上下文来评估来自Objective-C或Swift代码的JavaScript脚本,访问在JavaScript中定义或计算的值,以及使JavaScript可以访问本机对象、方法或函数。
创造JSContext对象
init 初始化一个新的JavaScript上下文。
initWithVirtualMachine: 创建与特定虚拟机关联的新JavaScript上下文。
评估脚本
evaluateScript: 执行指定的JavaScript代码。
evaluateScript:withSourceURL: 执行指定的JavaScript代码,将指定的URL视为其源位置。
检查正在运行的互斥量中的回调状态
currentContext 返回当前执行JavaScript代码的上下文。
currentCallee 返回当前执行的JavaScript函数。
currentThis 返回当前执行JavaScript代码中此关键字的值。
currentArguments 从JavaScript代码返回当前本机回调的参数。
使用JavaScript全局状态
globalObject 与上下文关联的JavaScript全局对象。
exception 在脚本的计算中抛出一个JavaScript异常。
exceptionHandler 要调用的块应该在抛出JavaScript异常时评估脚本结果。
virtualMachine 上下文所属的JavaScript虚拟机。
name 上下文的描述性名称。
使用下标访问JavaScript全局状态
- objectForKeyedSubscript:
返回上下文的全局对象中指定的JavaScript属性的值,允许使用下标getter语法。
- setObject:forKeyedSubscript:
设置上下文的全局对象的指定JavaScript属性,允许使用下标setter语法。
使用C JavaScriptCore API
JSGlobalContextRef 返回JavaScript上下文的C表示形式。
- contextWithJSGlobalContextRef: 从等价的C表示创建JavaScript上下文对象。
JSManagedValue
JSManagedValue对象封装JSValue对象,添加“条件保留”行为来提供值的自动内存管理。托管值的主要用例是将JavaScript值存储在Objective-C或Swift对象中,该对象本身被导出到JavaScript。
重要的
不要在导出到JavaScript的本机对象中存储非托管JSValue对象。因为JSValue对象引用它所包含的JSContext对象,所以这个操作创建了一个retain循环,从而避免释放上下文。
托管值的“条件保留”行为确保只要以下条件为真,托管值的底层JavaScript值就会被保留:
JavaScript值可以通过JavaScript对象图访问(即不受JavaScript垃圾收集的影响)
JSManagedValue对象可以通过Objective-C或Swift对象图访问,正如使用addManagedReference(_:withOwner:)方法向JavaScriptCore虚拟机报告的那样
但是,如果这两个条件都不为真,则托管值将其value属性设置为nil,释放底层JSValue对象。
创造Managed Value
- initWithValue:使用指定的JavaScript值初始化托管值。
managedValueWithValue:使用指定的JavaScript值创建托管值。
managedValueWithValue:andOwner:创建托管值并将其与所有者关联。
访问Managed Value
value---托管值的底层JavaScript值。
JSValue
JSValue实例是对JavaScript值的引用。您可以使用JSValue类在JavaScript和Objective-C或Swift表示之间转换基本值(比如数字和字符串),以便在本机代码和JavaScript代码之间传递数据。您还可以使用这个类来创建JavaScript对象,这些对象包装自定义类或JavaScript函数的本机对象,其实现由本机方法或块提供。
每个JSValue实例都源自一个JSContext对象,该对象表示包含该值的JavaScript执行环境。该值持有对其上下文对象的强引用——只要保留与特定JSContext实例相关联的任何值,该上下文就仍然是活动的。当在JSValue对象上调用实例方法,并且该方法返回另一个JSValue对象时,返回的值属于与原始值相同的上下文。
每个JavaScript值还与一个特定的JSVirtualMachine对象关联(通过上下文属性间接关联),该对象表示其上下文的底层执行资源集。您只能将JSValue实例传递给同一虚拟机承载的JSValue和JSContext实例上的方法,试图将一个值传递给另一个虚拟机会引发Objective-C异常。
在JavaScript和本机类型之间进行转换
当您使用以下方法创建JavaScript值、读取和转换JavaScript值时,JavaScriptCore会自动将本机值转换为JavaScript值,反之亦然,使用下面总结的规则。
1.NSDictionary对象或Swift字典及其包含的键将成为具有匹配的命名属性的JavaScript对象,反之亦然。键值是递归复制和转换的。
2.NSArray对象或Swift数组变成JavaScript数组,反之亦然,元素递归地复制和转换。
3.Objective-C块(或带有@convention(block)属性的Swift闭包)变成JavaScript函数对象,参数和返回类型使用与值相同的规则转换。转换由本机块或方法支持的JavaScript函数返回该块或方法;所有其他JavaScript函数都转换为空字典。
- 对于所有其他本机对象类型(以及类类型或元类型),JavaScriptCore创建一个JavaScript包装器对象,其中包含一个反映本机类层次结构的构造函数原型链。默认情况下,原生对象的JavaScript包装器不会使该对象的属性和方法在JavaScript中可用。
JSExport---将Objective-C类及其实例方法、类方法和属性导出到JavaScript代码的协议。
将Objective-C对象导出到JavaScript
当您从Objective-C类的实例创建JavaScript值,而JSValue类没有指定复制约定时,JavaScriptCore创建一个JavaScript包装器对象。(对于某些类,JavaScriptCore会自动将值复制到适当的JavaScript类型;例如,NSString实例变成JavaScript字符串。)
在JavaScript中,通过原型对象链支持继承。对于导出的每个Objective-C类,JavaScriptCore都在包含的JavaScript上下文(JSContext对象)中创建一个原型。对于NSObject类,prototype对象是JavaScript上下文的对象原型。对于所有其他Objective-C类,JavaScriptCore创建了一个prototype对象,它的内部[prototype]属性指向为Objective-C类的超类创建的prototype属性。因此,JavaScript包装器对象的原型链反映了被包装的Objective-C类型的继承层次结构。
除了原型对象之外,JavaScriptCore还为每个Objective-C类生成一个JavaScript构造函数对象。
将Objective-C方法和属性暴露给JavaScript
默认情况下,Objective-C类的任何方法或属性都不会暴露给JavaScript;相反,您必须选择要导出的方法和属性。对于类所遵循的每个协议,如果协议包含JSExport协议,那么JavaScriptCore将该协议解释为要导出到JavaScript的方法和属性列表。
对于导出的每个实例方法,JavaScriptCore都创建一个对应的JavaScript函数作为原型对象的属性。对于每个导出的Objective-C属性,JavaScriptCore都会在原型上创建一个JavaScript访问器属性。对于导出的每个类方法,JavaScriptCore在构造函数对象上创建一个JavaScript函数。例如,清单1和清单2演示了JSExport协议的采用,以及用JavaScript导出的类所表示的API。
实战------OC和JavaScript交互
test.html
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<div style="margin-top: 100px">
<h1>Objective-C和JavaScript交互</h1>
<input type="button" value="CallCamera" onclick="jsObj.callCamera()">
</div>
<div>
<input type="button" value="Share" onclick="callShare()">
</div>
<script>
varcallShare =function() {
varshareInfo = JSON.stringify({"title":"标题","desc":"内容", });
jsObj.share(shareInfo);//调用OC方法,含参数的
}
varpicCallback =function(photos) {
//picCallback--oc的回调方法 photos回调参数
alert(photos);
}
varshareCallback =function(){
alert('success1');
}
</html>
-----------------------JSModel.h-------------------
import <Foundation/Foundation.h>
import<JavaScriptCore/JavaScriptCore.h>
import<UIKit/UIKit.h>
@protocol JSExportDelegate <JSExport>//JSExportDelegate要继承JSExport
(void)callCamera;
(void)share:(NSString*)shareString;
// 在JS中调用时,函数名应该为showAlertMsg(arg1, arg2)
// 这里是只两个参数的。
- (void)showAlert:(NSString)title msg:(NSString)msg;
@end
@interface JSModel : NSObject<JSExportDelegate>
@property (nonatomic, strong) JSContext *jsContext;
@property (nonatomic, strong) UIWebView *webView;
@end
-----------------------JSModel.m------------------
import "JSModel.h"
@implementation JSModel
pragma mark---协议JSExportDelegate
-
(void)callCamera{
NSLog(@"callCamera");
// 获取到照片之后在回调js的方法picCallback把图片传出去
JSValue *picCallback =self.jsContext[@"picCallback"];//picCallback回调的方法,picCallback(H5的方法名)
[picCallback callWithArguments:@[@"photos"]];//返回的参数
}
-
(void)share:(NSString *)shareString{
NSLog(@"share:%@", shareString);
// 分享成功回调js的方法shareCallback---最好在子线程调用
JSValue *shareCallback =self.jsContext[@"shareCallback"];
[shareCallback callWithArguments:nil];
}
- (void)showAlert:(NSString *)title msg:(NSString *)msg{
}
@end
---------------------JSViewController.h--------------------------
import <UIKit/UIKit.h>
@interfaceJSViewController :UIViewController
@end
---------------------JSViewController.m-------------------------
import "JSViewController.h"
import<JavaScriptCore/JavaScriptCore.h>
import "JSModel.h"
@interface JSViewController ()<UIWebViewDelegate>
@property (nonatomic, strong) JSContext *jsContext;
@property (nonatomic, strong) UIWebView *mywebView;
@end
@implementationJSViewController
-
(void)viewDidLoad {
[super viewDidLoad];
[self initInterface];
[self initJavaScript];
}
pragma mark-----脚本
- (void)initJavaScript{
self.mywebView = [[UIWebView alloc]initWithFrame:CGRectMake(0, 6, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
self.mywebView.delegate =self;
NSURL* url= [NSURL URLWithString:[[NSBundle mainBundle]pathForResource:@"test" ofType:@"html"]];
[self.mywebView loadRequest:[[NSURLRequest alloc]initWithURL:url]];
[self.view addSubview:self.mywebView];
}
pragma mark--- UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView*)webView {
/*
关于交互对象的注入时机,在- (void)webViewDidFinishLoad:(UIWebView *)webView中去注入交互对象,但是这时候网页还没加载完,JavaScript那边已经调用交互方法,这样就会调不到原生应用的方法而出现问题。如果在创造一个UIWebView的子类,每个交互都在子类进行,在- (void)viewDidLoad中去注入交互对象,这样倒是解决了上面的问题,但是同时又引起了一个新的问题就是在一个网页内部点击链接跳转到另一个网页的时候,第二个页面需要交互,这时JSContext环境已经变化,但是- (void)viewDidLoad仅仅加载一次,跳转的时候,没有再次注入交互对象,这样就会导致第二个页面没法进行交互。所以针对这样的问题我的想法是在子类的 (void)viewDidLoad中添加一个定时器,每隔一定时间就注入一次交互对象。
*/
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
JSModel*model = [[JSModelalloc]init];
model.jsContext =self.jsContext;
model.webView = self.mywebView;
self.jsContext[@"jsObj"] = model ;// //注入JS代码,定义JS中需要调用的jsObj self.jsContext[@"jsObj"] = self 注入的交互对象为控制器self,这样JSContext环境引用控制器self,在退出控制器的时候,因为控制器self被JSContext引用而不释放,而JSContext只有等控制器释放了才能随之释放,所以就引起了循环引用,造成内存泄露。
self.jsContext.exceptionHandler= ^(JSContext*context,JSValue*exceptionValue) {
context.exception= exceptionValue;
NSLog(@"异常信息:%@", exceptionValue);
};
}
pragma mark---界面初始化
- (void)initInterface{
self.title=@"JS";
self.view.backgroundColor = [UIColor whiteColor];
}
@end