• Swift和Javascript的神奇魔法


    Swift和Javascript的神奇魔法

    记录Swift和Javascript如何进行交互

    前言

    今天在网上看到了一篇介绍Swift和Javascript交互的文章,感觉作者写的很好,因此把作者文章中的主要知识点进行一个总结。

    对于我个人而言,在项目中使用Javascript的原因有两个:

    • 某些任务,很可能已经有现成的Javascript库存在了,使用起来比原生实现更简单
    • 在架构上的考虑

    可以再这里下载演示demo

    demo中我们主要演示了3大块Swift和Javascript交互的神奇魔法:

    • 在Swift中获取和使用Javascript的属性和函数,处理Javascript的异常,在Javascript中获取和使用Swift的属性和函数
    • 使用Javascript第三方库Snowdown把Markdown文本转换成HTML文本
    • 使用Javascript解析复杂的数据,然后用Swift展示

    效果图:

    Model,Initial OS,Latest OS,Image URL
    iPhone (1st Generation),iPhone OS 1.0,iPhone OS 3.1.3,https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/IPhone_2G_PSD_Mock.png/81px-IPhone_2G_PSD_Mock.png
    iPhone 3G,iPhone OS 2.0,iOS 4.2.1,https://upload.wikimedia.org/wikipedia/commons/thumb/c/c6/IPhone_PSD_White_3G.png/81px-IPhone_PSD_White_3G.png
    iPhone 3GS,iPhone OS 3.0,iOS 6.1.6,https://upload.wikimedia.org/wikipedia/commons/thumb/c/c6/IPhone_PSD_White_3G.png/81px-IPhone_PSD_White_3G.png
    iPhone 4,iOS 4.0,iOS 7.1.2,https://upload.wikimedia.org/wikipedia/commons/thumb/5/59/IPhone_4_Mock_No_Shadow_PSD.png/81px-IPhone_4_Mock_No_Shadow_PSD.png
    iPhone 4S,iOS 5.0,iOS 9.3.5,https://upload.wikimedia.org/wikipedia/commons/thumb/d/d2/IPhone_4S_No_shadow.png/99px-IPhone_4S_No_shadow.png
    iPhone 5,iOS 6.0,iOS 10.2.1,https://upload.wikimedia.org/wikipedia/commons/thumb/f/fa/IPhone_5.png/99px-IPhone_5.png
    iPhone 5C,iOS 7.0,iOS 10.2.1,https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/IPhone_5C_%28blue%29.svg/88px-IPhone_5C_%28blue%29.svg.png
    iPhone 5S,iOS 7.0,iOS 10.2.1,https://upload.wikimedia.org/wikipedia/commons/thumb/5/5e/IPhone_5s.png/88px-IPhone_5s.png
    iPhone 6,iOS 8.0,iOS 10.2.1,https://upload.wikimedia.org/wikipedia/commons/thumb/0/01/IPhone6_silver_frontface.png/100px-IPhone6_silver_frontface.png
    iPhone 6 Plus,iOS 8.0,iOS 10.2.1,https://upload.wikimedia.org/wikipedia/commons/thumb/5/55/IPhone_6_Plus_Space_Gray.svg/120px-IPhone_6_Plus_Space_Gray.svg.png
    iPhone 6S,iOS 9.0,iOS 10.2.1,https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/IPhone_6S_Rose_Gold.png/105px-IPhone_6S_Rose_Gold.png
    iPhone 6S Plus,iOS 9.0,iOS 10.2.1,https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/IPhone_6S_Rose_Gold.png/125px-IPhone_6S_Rose_Gold.png
    iPhone SE,iOS 9.3,iOS 10.2.1,https://upload.wikimedia.org/wikipedia/en/thumb/d/d0/IPhone_SE_%28rose_gold%29.png/95px-IPhone_SE_%28rose_gold%29.png
    iPhone 7,iOS 10.0,iOS 10.2.1,https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/IPhone_7_Jet_Black.svg/105px-IPhone_7_Jet_Black.svg.png
    iPhone 7 Plus,iOS 10.0,iOS 10.2.1,https://upload.wikimedia.org/wikipedia/commons/thumb/6/64/IPhone_7_Plus_Jet_Black.svg/125px-IPhone_7_Plus_Jet_Black.svg.png
    

    把上边的数据解析后,展示为:

    Swift,Javascript的基本交互

    JavaScriptCore 中最主要的角色就是 JSContext 类。一个 JSContext 对象是位于 JavaScript 环境和本地 Javascript 脚本之间的桥梁。

    因此需要初始化一个JSContext对象:

    var jsContext: JSContext!
    

    我不会像原文那样一步一步的演示功能,我只是记录下使用JSContext的核心思想和用法。

    我们看看JSContext的初始化方法:

    func initializeJS() {
            self.jsContext = JSContext()
            
            /// Catch exception
            self.jsContext.exceptionHandler = { context, exception in
                if let ex = exception {
                    print("JS exception: " + ex.toString())
                }
            }
            
            let jsPath = Bundle.main.path(forResource: "jssource", ofType: "js")
            if let path = jsPath {
                do {
                    let jsSourceContents = try String(contentsOfFile: path)
                    jsContext.evaluateScript(jsSourceContents)
                } catch let ex {
                    print(ex.localizedDescription)
                }
            }
            
            // Configurate log
            let consoleLogObject = unsafeBitCast(self.consoleLog, to: AnyObject.self)
            jsContext.setObject(consoleLogObject, forKeyedSubscript: "consoleLog" as (NSCopying & NSObjectProtocol))
            jsContext.evaluateScript("consoleLog")
        }
    

    上边的代码中做了下边这几件事:

    • 使用JSContext()初始化JSContext对象
    • JSContext中有一个属性exceptionHandler用来监听Javascript的错误。这个属性很有用,我们使用这个属性来发现Javascript的错误
    • JSContext的evaluateScript方法可以把数据调入到JavaScriptCore的运行时环境中。该方法需要传递的参数是Javascript代码。返回值为Javascript代码中的最后一个JSValue。
    • let consoleLogObject = unsafeBitCast(self.consoleLog, to: AnyObject.self) unsafeBitCast用作强制类型转换,使用的时候需要明确的知道要转换的类型
    • open func setObject(_ object: Any!, forKeyedSubscript key: (NSCopying & NSObjectProtocol)!) 通过这种方式为Javascript添加属性或者函数

    那么,接下来,我们看一段Swift中获取Javascript属性的代码:

    func helloWorld() {
            if let valiableHW = jsContext.objectForKeyedSubscript("helloWorld") {
                print(valiableHW.toString())
            }
        }
    

    由上边的代码可以看出,通过函数open func objectForKeyedSubscript(_ key: Any!) -> JSValue!可以获取JSValue,然后使用toString()获取字符串。

    除了获取属性外,下边的代码演示了如何使用Javascript中的函数:

     func jsDemo1() {
            let firstName = "zhang"
            let lastName = "san"
            if let funcFullName = jsContext.objectForKeyedSubscript("getFullName") {
                if let fullName = funcFullName.call(withArguments: [firstName, lastName]) {
                    print(fullName)
                }
            }
        }
    

    通过函数open func objectForKeyedSubscript(_ key: Any!) -> JSValue!可以获取JSValue,然后调用call函数,并传递参数过去就实现了这个功能。

    我们在看看js代码中是如何使用Swift属性和函数的:

    function generateLuckyNumbers() {
        
        consoleLog("打印东东啊");
        
        var luckyNumbers = [];
        while (luckyNumbers.length != 6) {
            var randomNumber = Math.floor((Math.random() * 50) + 1);
            if (!luckyNumbers.includes(randomNumber)) {
                luckyNumbers.push(randomNumber);
            }
        }
        
        handleLuckyNumbers(luckyNumbers);
    }
    

    上边代码中的handleLuckyNumbers函数就是Swift中的函数,大家可以去demo中查看。

    Markdown文本转换成HTML文本

    这个文本转换最核心的内容就是解析Markdown的语法,然后输出HTML文本,如果我们自己手写转换代码,那就太麻烦了。Javascript已经有一个很强大的第三方库Snowdown。

    在JSContext的初始化方法中添加下边的代码:

    // Fetch and evaluate the Snowdown script.
    let snowdownScript = try String(contentsOf: URL(string: "https://cdn.rawgit.com/showdownjs/showdown/1.6.3/dist/showdown.min.js")!)
    self.jsContext.evaluateScript(snowdownScript)
    

    上边的代码中把转换脚本调入Javascript运行时,然后我们再通过下边的代码调用Javascript的代码:

    func convertMarkdownToHTML() {
            if let funcConvertMarkdownToHTML = jsContext.objectForKeyedSubscript("convertMarkdownToHTML") {
                funcConvertMarkdownToHTML.call(withArguments: [self.tvEditor.text])
            }
        }
    

    Javascript的代码如下:

    function convertMarkdownToHTML(source) {
        var converter = new showdown.Converter();
        var htmlResult = converter.makeHtml(source);
        consoleLog(htmlResult);
    }
    

    核心思想就是接受Javascript转换后的结果。

    自定义类和JavaScript

    前面,我们学习了如何暴露 Swift 程序代码给 JS,但 JavaScriptCore 的功能并不仅限于此。它还提供一种暴露自定义类的机制,并直接在 JS 中使用这些类的属性和函式。这就是 JSExport,它是一个协议,通过它你能够以更强大的方式来沟通 Swift 和 JS。

    我们看看自定义类的代码:

    import UIKit
    import JavaScriptCore
    
    @objc protocol DeviceInfoJSExport: JSExport {
        var model: String! { get set}
        var initialOS: String! { get set}
        var latestOS: String! { get set}
        var imageURL: String! { get set}
        
        static func initializeDevice(withModel: String) -> DeviceInfo
    }
    
    class DeviceInfo: NSObject, DeviceInfoJSExport {
        var model: String!
        var initialOS: String!
        var latestOS: String!
        var imageURL: String!
        
        init(withModel model: String) {
            super.init()
            
            self.model = model
        }
        
        class func initializeDevice(withModel: String) -> DeviceInfo {
            return DeviceInfo(withModel: withModel)
        }
        
        func concatOS() -> String {
            if let initial = initialOS {
                if let latest = latestOS {
                    return initial + "-" + latest
                }
            }
            return ""
        }
    }
    

    如果我们实现了JSExport协议,那么 JavaScript 运行时就能捕获该协议中的内容。对于这种设计,可以让我们很灵活的使用它的功能。

    再看看Javascript中关于这一段的核心代码:

    function parseiPhoneList(originalData) {
        var results = Papa.parse(originalData, { header: true });
        if (results.data) {
            var deviceData = [];
            
            for (var i=0; i < results.data.length; i++) {
                var model = results.data[i]["Model"];
                
                var deviceInfo = DeviceInfo.initializeDeviceWithModel(model);
                
                deviceInfo.initialOS = results.data[i]["Initial OS"];
                deviceInfo.latestOS = results.data[i]["Latest OS"];
                deviceInfo.imageURL = results.data[i]["Image URL"];
                
                deviceData.push(deviceInfo);
            }
            
            return deviceData;
        }
        
        return null;
    }
    

    上边的代码,调用了第三方解析库的函数,把数据解析出来后,生成deviceInfo数组,然后我们在Swift中就获取到了解析好的数据:

    func parseDeviceData() {
            if let path = Bundle.main.path(forResource: "iPhone_List", ofType: "csv") {
                do {
                    let contents = try String(contentsOfFile: path)
                    
                    if let functionParseiPhoneList = self.jsContext.objectForKeyedSubscript("parseiPhoneList") {
                        if let parsedDeviceData = functionParseiPhoneList.call(withArguments: [contents]).toArray() as? [DeviceInfo] {
                            self.deviceInfo = parsedDeviceData
                            self.tblDeviceList.reloadData()
                        }
                    }
                    
                }
                catch {
                    print(error.localizedDescription)
                }
            }
        }
    

    实现这些功能的基础就是Javascript的函数有返回值。

    总结

    在ios7之前我们只能通过UIWebview才能调用Javascript代码,现在,我们通过JavascriptCore可以自由使用Javascript。但在使用的时候要特别注意内存管理问题,大概需要注意一下两点:

    • 不要在block里面直接使用context,或者使用外部的JSValue对象。
    • 对象不要用属性直接保存JSValue对象,因为这样太容易循环引用了。

    可以使用JSManagedValue去解决这个问题。

    参考链接

    JavaScriptCore官方文档

    Using JavaScript in Swift Projects: Building a Markdown to HTML Editor

    如何在Swift项目中使用 Javascript编写一个将Markdown转为HTML的编辑器

    JavaScriptCore 使用

  • 相关阅读:
    计算属性computed和watch侦听器
    .gitignore不起作用
    flex 布局
    vue-awesome-swiper
    Chrome截长屏
    JS 数组 foreach 和 map
    for-in 和 for
    边框画的三角形给shadow
    element-UI 表单图片判空验证问题
    Vue 表格内容根据后台返回状态位填充文字
  • 原文地址:https://www.cnblogs.com/machao/p/6889935.html
Copyright © 2020-2023  润新知