http://ios.jobbole.com/89195/ https://blog.csdn.net/json_vip/article/details/51615029 http://www.swiftyper.com/2016/08/22/javascriptcore-basic/ https://blog.csdn.net/qq_30513483/article/details/52068400 https://juejin.im/post/5a955857f265da4e6f180f8d

名词介绍

JSVirtualMachine

Javascript 代码是在虚拟机当中运行的,每一个虚拟机由一个 JSVirtualMachine 来表示。一般情况下我们不用去手动创建 JSVirtualMachine 实例,使用系统提供的就足够了。

需要手动创建 JSVirtualMachine 的一个主要场景就是当我们需要并发地运行 Javascript 代码时,在单一的 JSVirtualMachine 里面是没办法同时运行多个线程的。

JSContext

一个 JSContext 对象就是 JavaScript 代码的运行环境,这和浏览器的 window 对象很相似。

在同一个 context 中,被加到这个 context 中的任何对象,都会可以被其他的对象无障碍的访问。实际上,是沙盒控制范围内的 JavaScript 的变量,方法,对象。你可以执行 JavaScript 和 OC/Swift 代码,访问 JavaScript 中的值,可以通过 JSContext 把一些值和对象,从 OC/Swift 传递到 JavaScript。

JSValue

JSValue 是 JavaScript 值的一个封装。它像一个桥梁一样可以使你在 JavaScript 和 OC/Swift 之间传递和共享数据。一个 JSValue 持有一个在他所属的 JSContext 的一个强引用。作为警告,你应该记住,如果你想在 OC/Swift 中存储 JSValue 的时候,将会产生循环引用。另外,为了访问底层的 JavaScript 对象,你可以使用 JSValue 创建一个被封装为 OC/Swift 对象的JavaScript 对象。同样,你也可以创建用 OC/Swift 写的 JavaScript 方法

JSManagedValue

Objective-C 或者 Swift 的对象都是使用引用计数,而 Javascript 则是使用垃圾回收机制。为了避免两种语言交互时产生的循环引用,需要使用 JSManagedValue 进行内存辅助管理。

JSExport

这是一个协议,使用这个协议可以将本地的对象导出对 JavaScript 对象,所有的本地属性与方法都会直接变成 JavaScript 的属性与方法。可以,这很魔法。

实战

通过JSContext直接执行JS代码

简单的函数方法运算,直接使用或者配置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
}

通过JSContext注入模型,然后调用模型的方法

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)
            }
        }
    }