Swift+JavaScriptCore

JavaScriptCore概述
JSValue: 代表一个JavaScript实体,一个JSValue可以表示很多JavaScript原始类型例如boolean, integers, doubles,甚至包括对象和函数。
JSManagedValue: 本质上是一个JSValue,但是可以处理内存管理中的一些特殊情形,它能帮助引用技术和垃圾回收这两种内存管理机制之间进行正确的转换。
JSContext: 代表JavaScript的运行环境,你需要用JSContext来执行JavaScript代码。所有的JSValue都是捆绑在一个JSContext上的。
JSExport: 这是一个协议,可以用这个协议来将原生对象导出给JavaScript,这样原生对象的属性或方法就成为了JavaScript的属性或方法,非常神奇。
JSVirtualMachine: 代表一个对象空间,拥有自己的堆结构和垃圾回收机制。大部分情况下不需要和它直接交互,除非要处理一些特殊的多线程或者内存管理问题。

Swift调用JavaScript

首先准备一份JavaScript代码
新建一个jsSource.js文件, 写入以下JavaScript代码

//一个变量
var helloWorld = "Hello World!"
//一个方法
function getFullname(firstname, lastname) {
    return firstname + " " + lastname;
}

Swift访问JavaScript变量

func swiftCallJS() {
        //构建一个JSContext对象
        let jsContext = JSContext()
        //加载JavaScript文件
        if let jsSourcePath = Bundle.main.path(forResource: "jsSource", ofType: "js") {
            do {
                //读取文件的代码
                let jsSourceContents = try String(contentsOfFile: jsSourcePath)
                //将JavaScript代码传递给JSContext环境
                _ = jsContext?.evaluateScript(jsSourceContents)
            }
            catch {
                print(error.localizedDescription)
            }
        }
        //通过JSContext访问JavaScript
        if let variableHelloWorld = jsContext?.objectForKeyedSubscript("helloWorld") {
            print(variableHelloWorld.toString())
        }
    }

Swift访问JavaScript方法

func swiftCallJSFunc() {
        
        //构建一个JSContext对象
        let jsContext = JSContext()
        //加载JavaScript文件
        if let jsSourcePath = Bundle.main.path(forResource: "jsSource", ofType: "js") {
            do {
                //读取文件的代码
                let jsSourceContents = try String(contentsOfFile: jsSourcePath)
                //将JavaScript代码传递给JSContext环境
                _ = jsContext?.evaluateScript(jsSourceContents)
            }
            catch {
                print(error.localizedDescription)
            }
        }

        
        let firstname = "Mickey"
        let lastname = "Mouse"
        //通过JSContext访问JavaScript, objectForKeyedSubscript()方法返回一个JSValue对象, JSValue对象包装了一个JavaScript实体
        if let functionFullname = jsContext?.objectForKeyedSubscript("getFullname") {
            //使用JSValue对象去掉用JavaScript方法, 并传递参数, 参数是以数组的方式传递
            if let fullname = functionFullname.call(withArguments: [firstname, lastname]) {
                print(fullname.toString())
            }
        }
    }

JavaScript调用Swift就是把Swift的闭包包装成一个对象, 并把这个对象传递给JSContext, 由JSContext对象进项JavaScript和Swift之间的调度

同样, 先在jsSource.js文件中加入以下JavaScript代码

//这个方法是通过swift调用的JS方法
function swiftCallJavaScript() {
    var luckyNumbers = [];
    
    while (luckyNumbers.length != 6) {
        var randomNumber = Math.floor((Math.random() * 50) + 1);
        
        if (!luckyNumbers.includes(randomNumber)) {
            luckyNumbers.push(randomNumber);
        }
    }
    
   //JavaScriptCallSwift方法是我们通过JavaScriptCore, 把该方法与Swift中定义的闭包进行绑定
    JavaScriptCallSwift(luckyNumbers);
}

首先先创建一个闭包的全局属性, 以便在全局范围内都可以访问到

//定义一个变量闭包, 参数是一个Int类型的数组, 无返回值
var swiftClosure:(@convention(block) ([Int]) -> Void)?

闭包是一个保存在栈区的代码块, 在使用闭包之前, 我们需要先对闭包进行定义
在viewDidLoad里面对变量闭包进行赋值

    override func viewDidLoad() {
        //第一步: 定义一个闭包
        //第一种定义方式
        let willCallClosure: @convention(block) ([Int]) -> Void = { luckyNumbers in
            print(luckyNumbers)
        }
        /**
        //第二种定义方式, 这种方式更简便
        let willCallClosure = {
            (luckyNumbers:[Int]) -> Void
            in
            print(luckyNumbers)
        }
        */
        //把闭包赋值给变量闭包
        swiftClosure = willCallClosure

        JavaScriptCallSwift()

 }

JavaScript访问Swift

    func JavaScriptCallSwift() {
        
        let jsContext = JSContext()
        jsContext?.exceptionHandler = { context, exception in
            if let exc = exception {
                print("JS Exception:", exc.toString())
            }
        }

        if let jsSourcePath = Bundle.main.path(forResource: "jsSource", ofType: "js") {
            do {
                let jsSourceContents = try String(contentsOfFile: jsSourcePath)
                _ = jsContext?.evaluateScript(jsSourceContents)
            }
            catch {
                print(error.localizedDescription)
            }
        }
        
        
        //第二步: 将闭包快转换成一个 AnyObject 对象
        let swiftAnyObject = unsafeBitCast(self.swiftClosure, to: AnyObject.self)
        /**
         * 第三步: 将swiftAnyObject 传递给 jsContext
         * 将闭包块与JS进行关联, JavaScriptCallSwift就是即将要执行的JS方法, 这个方法会调用都到swift的原生代码
         */
        jsContext?.setObject(swiftAnyObject, forKeyedSubscript: "JavaScriptCallSwift" as (NSCopying&NSObjectProtocol)!)
        //第四部: 通过JSContext计算JavaScriptCallSwift
        _ = jsContext?.evaluateScript("JavaScriptCallSwift")
        //第五步: 通过JSContext 调用方法
        if let functionGenerateLuckyNumbers = jsContext?.objectForKeyedSubscript("swiftCallJavaScript") {
            _ = functionGenerateLuckyNumbers.call(withArguments: nil)
        }

    }


以上就是JavaScript与Swift之间的交互, 是用的都是本地的JavaScript文件, 在实际工作中, 我们的JavaScript代码是从服务器获取的.

接下来介绍使用WebView与WKWebView不同的获取JavaScript内容的方法.

WebView获取JavaScript
extension NextVCViewController:UIWebViewDelegate {
    func webViewDidFinishLoad(_ webView: UIWebView) {
        
        let jsContext = webView.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as? JSContext
}

通过这种方式就可以获取JavaScript的内容了, 只需把上面的JavaScriptCallSwift()方法中生成JSContext的方法修改下就可以了. 在JavaScriptCallSwift()方法中我们是通过JSContext() 创建了一个JSContext, 次数我们同伙获取JavaScript创建一个JSContext.

WKWebView获取JavaScript

WKWebView是比较坑的是不能通过javaScriptCore进行交互, 所以就无法获取JSContext, 所以以上介绍的说有内容对于WKWebView来说就是沙滩之子(son of the bitch).

在WKWebView中使用WKUserContentController类来进行交互

首先我们先配置下WKWebView

    func configWebView() {
        let wkWebConfig = WKWebViewConfiguration.init()
        wkWebConfig.selectionGranularity = .character
        wkWebConfig.preferences = WKPreferences.init()
        wkWebConfig.preferences.minimumFontSize = 14
        wkWebConfig.preferences.javaScriptEnabled = true
        wkWebConfig.preferences.javaScriptCanOpenWindowsAutomatically = true
        wkWebConfig.userContentController = WKUserContentController.init()
        //这个是关键, 设置WKScriptMessageHandler的代理
        //AppModel是我们注入到JavaScript中的一个标识, JavaScript会通过这个标识来给Swift发送消息
        wkWebConfig.userContentController.add(self as WKScriptMessageHandler, name: "Bridge")
        
        let wkWeb = WKWebView(frame:CGRect(x: 0, y: 64, width: 200, height: 200), configuration: wkWebConfig);
        let path = Bundle.main.path(forResource: "web", ofType: "html")
        let url = NSURL(fileURLWithPath: path!)
        let request = NSURLRequest(url: url as URL)
        wkWeb.load(request as URLRequest)
        self.view.addSubview(wkWeb)
    }

然后实现与JavaScript通信的代理方法

extension WKWebViewController:WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if (message.name == "Bridge") {
            // 打印所传过来的参数,只支持NSNumber, NSString, NSDate, NSArray,
            // NSDictionary, and NSNull类型
            print(message.body);
        }
    }
}

接下来我们修改下JavaScript代码
这次使用加载html的方式来获取JavaScript

web.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
</head>
    <body>

        <script>
            //注册一个定时器, 模仿JavaScript调用
            var myVar=setInterval(function(){
                                    swiftCallJavaScript()
                                  },1000);
            function swiftCallJavaScript() {
            //这个是固定格式,  Bridgre就是刚才我们的标识
                window.webkit.messageHandlers.Bridge.postMessage({body: 'call swift'});
            }

        </script>
    </body>
</html>

这样就可以实现JavaScript调用Swift的代码了, 我们通过一个代理来获取JavaScript方法被调用的消息, 然后再进行响应的处理.

至于上面提到的 window.webkit.messageHandlers.Bridge.postMessage({body: 'call swift'});
为什么要这样写, 这个在苹果的源码里有注释

/*! @abstract Adds a script message handler.
 @param scriptMessageHandler The message handler to add.
 @param name The name of the message handler.
 @discussion Adding a scriptMessageHandler adds a function
 window.webkit.messageHandlers.<name>.postMessage(<messageBody>) for all
 frames.
 */
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,087评论 4 62
  • 嗯哼嗯哼蹦擦擦~~~ 转载自:https://github.com/Tim9Liu9/TimLiu-iOS 目录 ...
    philiha阅读 4,864评论 0 6
  • 本篇文章由我们团队的小马童鞋翻译完成,原文地址:From Swift to Javascript and Back...
    知识小集阅读 1,577评论 0 4
  • 我决定,去表白。 他们三面面相觑,交头接耳,似笑非笑,我突然感到一丝恐慌。 “可以,我支持你,反正早死晚死都得死。...
    南烽阅读 393评论 8 3
  • 邻居 一家之主和两个子女。 虽不曾让人省过心,但是,却是为了子女好。 家里老人和子孙后代,共享幸福生活。 我对年迈...
    H小芹菜阅读 120评论 0 0