當前位置:
首頁 > 知識 > 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展示

效果圖:

Swift和Javascript的神奇魔法

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的神奇魔法

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去解決這個問題。

參考鏈接
喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 達人科技 的精彩文章:

27_Redis:在Linux上的安裝、Jedis和常用命令
jquery源碼 Callback
Docker下搭建Jenkins構建環境
javascript——類和模塊
php jquery+ajax寫批量刪除

TAG:達人科技 |

您可能感興趣

Swift與Fedora
iOS swift UISearchBar拿到textfield控制項
基於Swift 5的編程教學Swift Playgrounds即將推出
Gigi Hadid和Taylor Swift超酷閨蜜裝
Swift For TensorFlow開源,敲響了Python的喪鐘?
谷歌又開源了:Swift for TensorFlow
介紹 Fedora 上的 Swift
Acer新品亮相:主打輕薄的Swift和四款Chromebook
Taylor Swift又瘦了!
蘋果 SwiftUI 踢館穀歌 Flutter
Swift中由找不到removeAll(where:)方法引起的連鎖反應(上)
Hermes愛馬仕 Bolide保齡球包 2H駝色 swift
湖人紫金配色!全新 Jordan Jumpman Swift 即將發售
宏碁Swift/Aspire與Spin PC系列產品全線更新
迷人的紫金裝扮!全新 Jordan Jumpman Swift 美國官網率先發售
Hermes愛馬仕 Bolide保齡球包 CK89黑色 swift
Hermes 愛馬仕 kelly Flat 35CM CK18大象灰 Swift
Taylor Swift 霉霉最新宣傳單曲《The Archer》 突襲上線!
Lover光速登頂,寫情歌的Taylor Swift一直沒變
傳奇戰靴重磅復刻!Air Jumpman Swift 現已海外發售