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去解决这个问题。
参考链接
Using JavaScript in Swift Projects: Building a Markdown to HTML Editor
如何在Swift项目中使用 Javascript编写一个将Markdown转为HTML的编辑器
Swift和Javascript的神奇魔法的更多相关文章
- D: Starry的神奇魔法(矩阵快速幂)
题目链接:https://oj.ismdeep.com/contest/Problem?id=1284&pid=3 D: Starry的神奇魔法 Time Limit: 1 s Me ...
- D.Starry的神奇魔法(矩阵快速幂)
/*D: Starry的神奇魔法 Time Limit: 1 s Memory Limit: 128 MB Submit My Status Problem Description ...
- javascript中神奇的(+)加操作符
javascript是一门神奇的语言,这没神奇的语言中有一个神奇的加操作符. 常用的加操作符我们可以用来做: 加法运算,例如:alert(1+2); ==>3 字符串连接,例如:alert(“a ...
- Javascript 中神奇的 this
Javascript 当中的 this 与其他语言是完全不同的机制,很有可能会让一些编写其他语言的工程师迷惑. 1. 误以为 this 指向函数自身 根据 this 的英语语法,很容易将函数中出现的 ...
- 学好三角学(函数) — SWIFT和JAVASCRIPT游戏开发的必备技能 iFIERO.com
不论是使用哪种平台进行开发,三角学在游戏当中都被广泛的使用,因此,小编iFERO认为,三角学是必须得掌握的技能之一. 游戏图片由 摘自 Razeware LLC 先以Javascript为例 一.角度 ...
- 30分钟快速搭建Web CRUD的管理平台--django神奇魔法
加上你的准备的时间,估计30分钟完全够用了,因为最近在做爬虫管理平台,想着快速开发,没想到python web平台下有这么非常方便的框架,简洁而优雅.将自己的一些坑总结出来,方便给大家的使用. 准备环 ...
- 在 Swift 專案中使用 Javascript:編寫一個將 Markdown 轉為 HTML 的編輯器
原文:Using JavaScript in Swift Projects: Building a Markdown to HTML Editor 作者:GABRIEL THEODOROPOULOS ...
- [Cordova-IOS]JavaScript与Swift交互
[Cordova-IOS]Swift调用JavaScript中的函数 概述 Cordova中,通过插件的形式可以实现JavaScript与Swift的交互,关于Cordova插件的定义以及Swfit如 ...
- JavaScript闭包,只学这篇就够了
# 闭包不是魔法 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义. 其实只要理解了核心概念,闭包并不是那么的难于理解.但是,网上充斥了太多学术性的 ...
随机推荐
- XJOIWinterCampPrecontest1-P2队列
2 排队2.1 题目有n 个人打水,第i 个人打水需要ai 的时间.有K 个水龙头,你可以随意安排他们打水的顺序以及使用哪一个水龙头,但是每一时刻每一个水龙头只能有一个人使用且一个人一旦开始打水就不能 ...
- POPTEST老李分享修改dns ip的vbs代码
POPTEST老李分享修改dns ip的vbs代码 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨 ...
- 1.Redis 的安装
一.Redis 介绍 Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API. 作为Key-value型数据库,Red ...
- ElasticSearch查询 第五篇:布尔查询
布尔查询是最常用的组合查询,不仅将多个查询条件组合在一起,并且将查询的结果和结果的评分组合在一起.当查询条件是多个表达式的组合时,布尔查询非常有用,实际上,布尔查询把多个子查询组合(combine)成 ...
- 关于laravel框架的跨域请求/jsonp请求的理解
最近刚接触laravel框架,首先要写一个跨域的单点登录.被跨域的问题卡了两三天,主要是因为对跨域这快不了解,就在刚才有点茅塞顿开的感觉,我做一下大概整理,主要给一些刚接触摸不着头脑的看,哪里写得不对 ...
- USACO Section 1.1-3 Friday the Thirteenth
Friday the Thirteenth 黑色星期五 13号又是一个星期五.13号在星期五比在其他日子少吗?为了回答这个问题,写一个程序,要求计算每个月的十三号落在周一到周日的次数. 给出N年的一个 ...
- Linux学习(一)
Linux系统 1.组成部分 1.1内核负责的功能 1.1.1:系统内存管理 内存管理即管理物理内存和虚拟内存 (通过硬盘实现的,即swap space),长时间为被访问的内存块会被放到虚拟内存中,当 ...
- Bitbucket导入项目
1.先初始化git本地仓库(如果已经受git管理跳过此步) $ git init 2.添加远程仓库 $ git remote add origin git@bitbucket.org:name/rep ...
- 2017-4-24 WinForm开发基础、窗体的属性CenterScreen
WinForm中文名称: Windows窗体,是·Net开发平台中对Windows Form的一种称谓. 客户端应用程序:C/S 客户端很重要的特点:可以操作用户电脑上的文件 窗体属性:窗体种类: + ...
- Java多线程中线程间的通信
一.使用while方式来实现线程之间的通信 package com.ietree.multithread.sync; import java.util.ArrayList; import java.u ...