-
Notifications
You must be signed in to change notification settings - Fork 0
Swift与JSContext的交互
Javascript 代码是在虚拟机当中运行的,每一个虚拟机由一个 JSVirtualMachine 来表示。一般情况下我们不用去手动创建 JSVirtualMachine 实例,使用系统提供的就足够了。
需要手动创建 JSVirtualMachine 的一个主要场景就是当我们需要并发地运行 Javascript 代码时,在单一的 JSVirtualMachine 里面是没办法同时运行多个线程的。
一个 JSContext 对象就是 JavaScript 代码的运行环境,这和浏览器的 window 对象很相似。
在同一个 context 中,被加到这个 context 中的任何对象,都会可以被其他的对象无障碍的访问。实际上,是沙盒控制范围内的 JavaScript 的变量,方法,对象。你可以执行 JavaScript 和 OC/Swift 代码,访问 JavaScript 中的值,可以通过 JSContext 把一些值和对象,从 OC/Swift 传递到 JavaScript。
JSValue 是 JavaScript 值的一个封装。它像一个桥梁一样可以使你在 JavaScript 和 OC/Swift 之间传递和共享数据。一个 JSValue 持有一个在他所属的 JSContext 的一个强引用。作为警告,你应该记住,如果你想在 OC/Swift 中存储 JSValue 的时候,将会产生循环引用。另外,为了访问底层的 JavaScript 对象,你可以使用 JSValue 创建一个被封装为 OC/Swift 对象的JavaScript 对象。同样,你也可以创建用 OC/Swift 写的 JavaScript 方法
Objective-C 或者 Swift 的对象都是使用引用计数,而 Javascript 则是使用垃圾回收机制。为了避免两种语言交互时产生的循环引用,需要使用 JSManagedValue 进行内存辅助管理。
这是一个协议,使用这个协议可以将本地的对象导出对 JavaScript 对象,所有的本地属性与方法都会直接变成 JavaScript 的属性与方法。可以,这很魔法。
简单的函数方法运算,直接使用或者配置objectForKeyedSubscript使用:
let jsContext = JSContext()
let res = jsContext?.evaluateScript("1+3")
print(res) // Optional(4)
jsContext?.evaluateScript("var num1 = 10; var num2 = 20;")
jsContext?.evaluateScript("function sum(param1, param2) { return param1 + param2; }")
//evaluateScript配合方法名
let res2 = jsContext?.evaluateScript("sum(num1, num2)")
print(res2) //Optional(30)
//通过下标来获取js方法并调用方法
let sumFunc = jsContext?.objectForKeyedSubscript("sum")
let res3 = sumFunc?.call(withArguments: [10,20])
print(res3) //Optional(30)使用jsonlint.js
func jsFunction(){
let jsvm = JSVirtualMachine()
let context = JSContext(virtualMachine: jsvm)
let path = Bundle.main.path(forResource: "jsonlint", ofType: "js", inDirectory: nil)
let string = try! String.init(contentsOfFile: path!)
context?.evaluateScript(string)
context?.exceptionHandler = { a /*JSContext?*/ , b /*JSValue *exception */ in
print(b!) //打印错误
}
//let fn = context?.evaluateScript("jsonlint.parse('[\"a\",\"cc\"]');")
let fn = context?.evaluateScript("jsonlint.parse('{\"a\":123}');")
print(fn) //格式不对出现undefined
}1). 首先定义一个协议SwiftJavaScriptDelegate 该协议必须遵守JSExport协议 2). 然后定义一个模型 该模型实现SwiftJavaScriptDelegate协议 (这里注意,如果有更改 UI 的需求,那么需要回到主线程。因为调用不在主线程)
例子来源 https://juejin.im/post/5a955857f265da4e6f180f8d
html文件
<body>
<br><br><br>
<div class="btn-block" onclick="WebViewJavascriptBridge.popVC()">
js调用App的返回方法 popVC()
</div>
<div class="content">演示最基本的调用</div>
<div class="btn-block" onclick="WebViewJavascriptBridge.showDic({
'title' : '字典传值,PierceDark 的博客',
'description' : '欢迎交流学习',
'url' : 'https://www.jianshu.com/u/50bd017bb4ba'
})">js调用App的showDic()</div>
<div class="content">演示字典参数的使用</div>
<div class="btn-block" onclick="WebViewJavascriptBridge.showDialogMessage('此篇参考自马燕龙个人博客', 'https://www.jianshu.com/p/c11f9766f8d5')">
js调用App的弹出对话框方法showDialog()
</div>
<div class="content">演示传递多个参数的使用,注意js调用时的方法名</div>
<div class="btn-block" onclick="WebViewJavascriptBridge.callHandler('jsHandlerFunc')">
js调用App的方法后 App再调用js函数执行回调
</div>
<div class="content" id="js-content">App调用js函数执行回调时 内容会改变</div>
<script type="text/javascript">
function jsHandlerFunc(argument) {
document.getElementById('js-content').innerHTML = "App调用js回调函数啦, 我是" + argument['name'];
}
</script>
</body>swift文件:
import UIKit
import JavaScriptCore
@objc protocol SwiftJSDelegate: JSExport{
//js调用app的返回方法
func popVC()
// js调用App的showDic。传递Dict 参数
func showDic(_ dict:[String : AnyObject])
// js调用App方法时传递多个参数 并弹出对话框 注意js调用时的函数名
func showDialog(_ title: String, message: String)
// js调用App的功能后 App再调用js函数执行回调
func callHandler(_ handleFuncName: String)
}
class SwiftJSModel: NSObject, SwiftJSDelegate {
weak var jsContext: JSContext?
func popVC() {
print("popvc")
}
func showDic(_ dict: [String : AnyObject]) {
print("showDic", dict)
}
func showDialog(_ title: String, message: String) {
print("showDialog")
}
func callHandler(_ handleFuncName: String) {
print("callHandler")
let jsHandlerFunc = self.jsContext?.objectForKeyedSubscript("\(handleFuncName)")
let dict = ["name": "sean", "age": 18] as [String : Any]
let _ = jsHandlerFunc?.call(withArguments: [dict])
}
}WebViewDelegate:
func webViewDidFinishLoad(_ webView: UIWebView) {
setContext()
}
func setContext() {
if let webView = self.webView {
let context = webView.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as! JSContext
let model = SwiftJSModel()
//model.controller = self
model.jsContext = context
// 这一步是将SwiftJavaScriptModel模型注入到JS中,在JS就可以通过WebViewJavascriptBridge调用我们暴露的方法了。
context.setObject(model, forKeyedSubscript: "WebViewJavascriptBridge" as NSCopying & NSObjectProtocol)
// 注册到网络Html页面 请设置允许Http请求
let curUrl = webView.request?.url?.absoluteString //WebView当前访问页面的链接 可动态注册
context.evaluateScript(curUrl)
context.exceptionHandler = { (context, exception) in
print("exception:", exception as Any)
}
}
}