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 exceptionself.jsContext.exceptionHandler = { context, exception inif 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 loglet 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 = parsedDeviceDataself.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 使用

Swift和Javascript的神奇魔法相关推荐

  1. D: Starry的神奇魔法(矩阵快速幂)

    题目链接:https://oj.ismdeep.com/contest/Problem?id=1284&pid=3 D: Starry的神奇魔法 Time Limit: 1 s      Me ...

  2. 计算机编程飞船,当光速飞船遇上“神奇魔法”……

    原标题:当光速飞船遇上"神奇魔法"-- 活力朱紫 当光速飞船遇上"神奇魔法" 海峡少儿艺术展 0101少儿编程俱乐部 变机器人.变房子 你喜欢啥?都能用它搭建! ...

  3. D.Starry的神奇魔法(矩阵快速幂)

    /*D: Starry的神奇魔法 Time Limit: 1 s      Memory Limit: 128 MB Submit My Status Problem Description     ...

  4. 问题 C: LD的神奇魔法

    问题 C: LD的神奇魔法 时间限制: 1 Sec  内存限制: 128 MB 题目描述 输入 输出 样例输入 4 123 2345 98 12 2188 8311 114514 1919810 样例 ...

  5. 打造迪士尼梦幻体验的神奇魔法

    一个美妙的童话乐园和奇幻的魔法世界,它带给人们无穷的想象力.美轮美奂的视觉享受.发自内心的快乐.迪士尼乐园像是用"撒落仙尘"(pixie dust),创造一场秀--这场秀在迪士尼乐 ...

  6. chatgpt赋能python:Python一行输出8个数的神奇魔法!

    Python一行输出8个数的神奇魔法! 作为一位有10年Python编程经验的工程师,我想与大家分享一种神奇的方法,利用Python的一行代码输出8个数.这个方法简单易懂,并且非常实用,适合初学者和专 ...

  7. TensorFlow AI 新品更易用!联手NVIDIA,支持Swift和JavaScript

     整理 | 费棋 出品 | AI科技大本营(公众号ID:rgznai100) 天体物理学家使用 TensorFlow 分析开普勒任务中的大量数据,以发现新的行星: 医学研究人员利用 TensorFl ...

  8. [swift]-使用JavaScript解决WKWebView无法发送POST参数问题

    2019独角兽企业重金招聘Python工程师标准>>> 基本实现思路: 将一个包含JavaScript的POST请求的HTML代码放到工程目录中 加载这个包含JavaScript的P ...

  9. javascript中神奇的(+)加操作符

    javascript是一门神奇的语言,这没神奇的语言中有一个神奇的加操作符. 常用的加操作符我们可以用来做: 加法运算,例如:alert(1+2); ==>3 字符串连接,例如:alert(&q ...

最新文章

  1. iOS 9音频应用播放音频之控制播放速度
  2. 关于业务系统的架构思考
  3. 计算机考研只有数据结构,【择校必看】十三所计算机专业课只考数据结构的985院校!...
  4. 服务器 远程存储,数据储存——远程服务器存储——框架方式
  5. Win11系统各个版本之间的比较,看完你就懂了!
  6. 关于局部变量在内存中的地址
  7. 充分使用表格标签(Table)
  8. robocopy的退出返回代码
  9. [码海拾贝 之TC] 使用View 定义动态的Class
  10. dhcp工具_自制的树莓派网络工具集
  11. Python_多进程
  12. 运维技术之二(2)、docker安装jumpserver
  13. 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_07 缓冲流_2_BufferedOutputStream_字节缓冲...
  14. 制作u盘winpe启动盘_u盘启动盘制作工具 纯净+好用,原来不止是 微pe
  15. iOS闪退日志的收集和解析
  16. 拔丝芋头的Java学习日记--Day4
  17. 极速接入港交所OMD-C 港股L2数据,JAR WebSocket API获取数据
  18. vue+pdf.js 印章签署完后鼠标滑过显示签章信息
  19. 2021年山阳中学高考成绩查询,陕西省山阳中学2018年高考成绩公布,2018年高考再创辉煌!...
  20. CSDN博主排行榜上线!

热门文章

  1. RHEL6 下安装tmux
  2. 2010年最火与最冷的IT职业
  3. 混合云存储组合拳:基于云存储网关与混合云备份的OSS数据备份方案
  4. linux symbolic link attack tutorial
  5. ACM Computer Factory
  6. 项目展示文案生成设计
  7. 5 Best User Interface Design Pattern Libraries
  8. 使用 DES 算法对数据加密
  9. JAVA IO - 删除特定扩展名的文件
  10. 摩卡业务服务管理(Mocha BSM)解决方案