鍍金池/ 教程/ iOS/ Playground 快速原型制作
與四軸無人機的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進的自動布局工具箱
動畫
為 iOS 7 重新設(shè)計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應(yīng)式 Android 應(yīng)用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項目介紹
Swift 的強大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學習
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學習的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機與照片
音頻 API 一覽
交互式動畫
常見的后臺實踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應(yīng)式視圖
Square Register 中的擴張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計的藝術(shù)
導航應(yīng)用
線程安全類的設(shè)計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學無止境
XCTest 測試實戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機捕捉
語言標簽
同步案例學習
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機工作原理
Build 過程

Playground 快速原型制作

由于使用 Cocoa 框架能夠快速地創(chuàng)建一個可用的應(yīng)用,這讓許多開發(fā)者都喜歡上了 OS X 或 iOS 開發(fā)。如今即使是小團隊也能設(shè)計和開發(fā)復雜的應(yīng)用,這很大程度上要歸功于這些平臺所提供的工具和框架。Swift 的 Playground 不僅繼承了快速開發(fā)的傳統(tǒng),并且有改變我們設(shè)計和編寫 OS X 和 iOS 應(yīng)用方式的潛力。

向那些還不熟悉這個概念的讀者解釋一下,Swift 的 playground 就像是一個可交互的文檔,在其中你可以輸入 Swift 代碼讓它們立即編譯執(zhí)行。操作結(jié)果隨著執(zhí)行的時間線一步步被展示,開發(fā)者能在任何時候輸出和監(jiān)視變量。Playground 既可以在現(xiàn)有的 Xcode 工程中進行創(chuàng)建,也能作為單獨的包存在。

Swift 的 playground 主要還是作為學習這門語言的工具而被重視,然而我們只要關(guān)注一下類似項目,如 IPython notebooks,就能看到交互編程環(huán)境在更廣闊的范圍內(nèi)的潛在應(yīng)用。從科學研究機器視覺實驗,這些任務(wù)現(xiàn)在都使用了 IPython notebooks。這種方式也被用來探索其他語言的范例,如 Haskell 的函數(shù)式編程。

接下來我們將探索 Swift 的 playground 在文檔、測試和快速原型方面的用途。本文使用的所有 Swift playground 源碼可以在這里下載

將 Playground 用于文檔和測試

Swift 是一個全新的語言,許多人都使用 playground 來了解其語法和約定。不光是語言,Swift 還提供了一個新的標準庫。目前這個標準庫的文檔中對于方法的說明不太詳細,所以雨后春筍般的涌現(xiàn)了許多像 practicalswift.org 標準庫方法列表這樣的資源。

編者注這里有一份自動生成和整理的 Swift 標準庫文檔,可以作為參考。

不過通過文檔知道方法的作用是一回事,在代碼中實際調(diào)用又是另一回事。特別是許多方法在新語言 Swift 的 collection class 中能表現(xiàn)出有趣的特性,因此如果能在 collections 里實際檢驗它們的作用將非常有幫助。

Playground 展示語法和實時執(zhí)行真實數(shù)據(jù)的特性,為編寫方法和庫接口提供了很好的機會。為了介紹 Collection 方法的使用,我們創(chuàng)建了一個叫 CollectionOperations.playground 的例子,其中包含了一系列 collection 方法的例子,所有的樣例數(shù)據(jù)都能實時修改。

例如,我們創(chuàng)建了如下的初始數(shù)組:

let testArray = [0, 1, 2, 3, 4]

然后想試試 filter() 方法:

let odds = testArray.filter{$0 % 2 == 1}

最后一行顯示這個操作所得到的結(jié)果的數(shù)組為: [1, 3]。通過實時編譯我們能了解語法、寫出例子以及獲得方法如何使用的說明,所有這些就如一個活的文檔展示在眼前。

這對于其他的蘋果框架和第三方庫都奏效。 例如,你可能想給其他人展示如何使用 Scene Kit,這是蘋果提供的一個非常棒的框架,它能在 Mac 和 iOS 上快速構(gòu)建3D場景?;蛟S你會寫一個示例應(yīng)用,不過這樣展示的時候就要構(gòu)建和編譯。

在例子 SceneKitMac.playground 中,我們已經(jīng)建立了一個功能完備帶動畫的 3D 場景。你需要打開 Assistant Editor (在菜單上依次點擊 View | Assistant Editor | Show Assistant Editor),3D 效果和動畫將會被自動渲染。這不需要編譯循環(huán),而且任何的改動,比如改變顏色、幾何形狀、亮度等,都能實時反映出來。使用它能在一個交互例子中很好的記錄和介紹如何使用框架。

除了展示方法和方法的操作,你還會注意到通過檢查輸出的結(jié)果,我們可以驗證一個方法的執(zhí)行是否正確,甚至在加載到 playground 的時候就能判斷方法是否被正確解析。不難想象我們也可以在 playground 里添加斷言,以及創(chuàng)建真正的單元測試?;蛘吒M一步,創(chuàng)建出符合條件的測試,從而在你打字時就實現(xiàn)測試驅(qū)動開發(fā)。

事實上,在 2014 年 7 月號的 PragPub 雜志中,Ron Jeffries 在他的文章 “從測試驅(qū)動開發(fā)角度來看Swift” 中提到過這一觀點:

Playground 很大程度上會對我們?nèi)绾螆?zhí)行測試驅(qū)動開發(fā)產(chǎn)生影響。Playground 能夠快速展示我們所能做的東西,因此我們將比之前走得更快。但是同過去的測試驅(qū)動開發(fā)框架結(jié)合在一起時,能否走的更好?我們是否能提煉出更好的代碼,以滿足更少的缺陷數(shù)量和重構(gòu)?

關(guān)于代碼質(zhì)量的問題還是留給別人回答吧,接下來我們一起來看看 playground 如何加快一個快速原型的開發(fā)。

創(chuàng)建 Accelerate 的原型 -- 經(jīng)過優(yōu)化的信號處理

Accelerate 框架包括了許多功能強大的并行處理大型數(shù)據(jù)集的方法。這些方法可以利用例如 Intel 芯片中的 SSE 指令集,或者 ARM 芯片中的 NEON 技術(shù)等,這樣的現(xiàn)代 CPU 中矢量處理指令的優(yōu)勢。然而,相較于功能的強大,它們的接口似乎有點不透明,其使用的文檔也有點缺乏。這就導致許多開發(fā)者無法使用 Accelerate 這個強大的工具所帶來的優(yōu)勢。

Swift 提供了一個機會,通過方法重載或為 Accelerate 框架進行包裝后,可以讓交互更加容易。這已經(jīng)在 Chris Liscio 的庫 SMUGMath 的實踐中被證實,這也正是我們接下來將要創(chuàng)建的原型的靈感來源。

假設(shè)你有一系列正弦波的數(shù)據(jù)樣本,然后想通過這些數(shù)據(jù)來確定這個正弦波的頻率和幅度,你會怎么做呢?一個解決方案是通過傅里葉變換來算出這些值,傅里葉變換能從一個或多個重疊的正弦波提取頻率和幅度信息。Accelerate 框架提供了另一個解決方案,叫做快速傅里葉變換 (FFT),關(guān)于這個方案這里有一個 (基于 IPython notebook 的) 很好的解釋。

我們在例子 AccelerateFunctions.playground 中實現(xiàn)了這個原型,你可以對照這個例子來看下面的內(nèi)容。請確認你已經(jīng)打開 Assistant Editor (在菜單上依次點擊 View | Assistant Editor | Show Assistant Editor) 以查看每一階段所產(chǎn)生的圖形。

首先我們要產(chǎn)生一些用于實驗的示例波形。使用 Swift 的 map() 方法可以很容易地實現(xiàn):

let sineArraySize = 64

let frequency1 = 4.0
let phase1 = 0.0
let amplitude1 = 2.0
let sineWave = (0..<sineArraySize).map {
    amplitude1 * sin(2.0 * M_PI / Double(sineArraySize) * Double($0) * frequency1 + phase1)
}

為了便于之后使用 FFT,我們的初始數(shù)組大小必須是 2 的冪次方。把 sineArraySize 值改為像 32,128 或 256 將改變之后顯示的圖像的密度,但它不會改變計算的基本結(jié)果。

要繪制我們的波形,我們將使用新的 XCPlayground 框架 (需要先導入) 和以下輔助函數(shù):

func plotArrayInPlayground<T>(arrayToPlot:Array<T>, title:String) {
    for currentValue in arrayToPlot {
        XCPCaptureValue(title, currentValue)
    }
}

當我們執(zhí)行:

plotArrayInPlayground(sineWave, "Sine wave 1")

我們可以看到如下所示的圖表:

http://wiki.jikexueyuan.com/project/objc/images/16-1.png" alt="" />

這是一個頻率為 4.0、振幅為 2.0、相位為 0 的正弦波。為了變得更有趣一些,我們創(chuàng)建了第二個正弦波,它的頻率為 1.0、振幅為 1.0、相位為 π/2,然后把它疊加到第一個正弦波上:

let frequency2 = 1.0
let phase2 = M_PI / 2.0
let amplitude2 = 1.0
let sineWave2 = (0..<sineArraySize).map {
    amplitude2 * sin(2.0 * M_PI / Double(sineArraySize) * Double($0) * frequency2 + phase2)
}

http://wiki.jikexueyuan.com/project/objc/images/16-2.png" alt="" />

現(xiàn)在我們要將兩個波疊加。從這里開始 Accelerate 將幫助我們完成工作。將兩個個獨立地浮點數(shù)數(shù)組相加非常適進行合并行處理。這里我們要使用到 Accelerate 的 vDSP 庫,它正好有這類功能的方法。為了讓這一切更有趣,我們將重載一個 Swift 操作符用于向量疊加。不巧的是 + 這個操作符已經(jīng)用于數(shù)組連接 (其實挺容易混淆的),而 ++ 更適合作為遞增運算符,因此我們將定義 +++ 作為相加的運算符。

infix operator  +++ {}
func +++ (a: [Double], b: [Double]) -> [Double] {
    assert(a.count == b.count, "Expected arrays of the same length, instead got arrays of two different lengths")

    var result = [Double](count:a.count, repeatedValue:0.0)
    vDSP_vaddD(a, 1, b, 1, &result, 1, UInt(a.count))
    return result
}

上文定義了一個操作符,操作符能將兩個 Double 類型的 Swift 數(shù)組中的元素依次合并為一個數(shù)組。在運算中創(chuàng)建了一個和輸入的數(shù)組長度相等的空白數(shù)組(假設(shè)輸入的兩個數(shù)組長度相等)。由于 Swift 的一維數(shù)組可以直接映射成 C 語言的數(shù)組,因此我們只需要將作為參數(shù)的 Doubles 類型數(shù)組直接傳遞給 vDSP_vaddD() 方法,并在我們的數(shù)組結(jié)果前加前綴 &。

為了驗證上述疊加是否被正確執(zhí)行,我們可以使用 for 循環(huán)以及 Accelerate 方法來繪制合并后的正弦波的結(jié)果:

var combinedSineWave = [Double](count:sineArraySize, repeatedValue:0.0)
for currentIndex in 0..<sineArraySize {
    combinedSineWave[currentIndex] = sineWave[currentIndex] + sineWave2[currentIndex]
}

let combinedSineWave2 = sineWave +++ sineWave2

plotArrayInPlayground(combinedSineWave, "Combined wave (loop addition)")
plotArrayInPlayground(combinedSineWave2, "Combined wave (Accelerate)")

http://wiki.jikexueyuan.com/project/objc/images/16-3.png" alt="" />

果然,結(jié)果是一致的。

在繼續(xù) FFT 本身之前,我們需要另一個向量運算來處理計算的結(jié)果。Accelerate 的 FFT 實現(xiàn)中獲取的所有結(jié)果都是平方之后的,所以我們需要對它們做平方根操作。我們需要對數(shù)組中的所有元素調(diào)用類似 sqrt() 方法,這聽上去又是一個使用 Accelerate 的機會。

Accelerate 的 vecLib 庫中有很多等價的數(shù)學方法,包括平方根的 vvsqrt()。這是個使用方法重載的好例子,讓我們來創(chuàng)建一個新版本的 sqrt(),使其能處理 Double 類型的數(shù)組。

func sqrt(x: [Double]) -> [Double] {
    var results = [Double](count:x.count, repeatedValue:0.0)
    vvsqrt(&results, x, [Int32(x.count)])
    return results
}

和我們的疊加運算符一樣,重載的平方函數(shù)輸入一個 Double 數(shù)組,為輸出創(chuàng)建了一個 Double 類型的數(shù)組,并將輸入數(shù)組中的所有參數(shù)直接傳遞給 Accelerate 中的 vvsqrt()。通過在 playground 中輸入以下代碼,我們可以驗證剛剛重載的方法。

sqrt(4.0)
sqrt([4.0, 3.0, 16.0])

我們能看到,標準 sqrt() 函數(shù)返回2.0,而我們的新建的重載方法返回了 [2.0, 1.73205080756888, 4.0]。這的確是一個非常易用的重載方法,你甚至可以想象照以上方法使用 vecLib 為所有的數(shù)學方法寫一個并行的版本 (不過 Mattt Thompson 已經(jīng)做了這件事)。在一臺 15 寸的 2012 年中的 i7 版本 MacBook Pro 中處理一個有一億個元素的數(shù)組,使用基于 Accelerate 的 sqrt() 方法的運行速度比迭代使用普通的一維 sqrt() 快將近一倍。

有了這個以后,我們來實現(xiàn) FFT。我們并不打算在 FFT 設(shè)置的細節(jié)上花費大量時間,以下是我們的 FFT 方法:

let fft_weights: FFTSetupD = vDSP_create_fftsetupD(vDSP_Length(log2(Float(sineArraySize))), FFTRadix(kFFTRadix2))

func fft(var inputArray:[Double]) -> [Double] {
    var fftMagnitudes = [Double](count:inputArray.count, repeatedValue:0.0)
    var zeroArray = [Double](count:inputArray.count, repeatedValue:0.0)
    var splitComplexInput = DSPDoubleSplitComplex(realp: &inputArray, imagp: &zeroArray)

    vDSP_fft_zipD(fft_weights, &splitComplexInput, 1, vDSP_Length(log2(CDouble(inputArray.count))), FFTDirection(FFT_FORWARD));
    vDSP_zvmagsD(&splitComplexInput, 1, &fftMagnitudes, 1, vDSP_Length(inputArray.count));

    let roots = sqrt(fftMagnitudes) // vDSP_zvmagsD returns squares of the FFT magnitudes, so take the root here
    var normalizedValues = [Double](count:inputArray.count, repeatedValue:0.0)

    vDSP_vsmulD(roots, vDSP_Stride(1), [2.0 / Double(inputArray.count)], &normalizedValues, vDSP_Stride(1), vDSP_Length(inputArray.count))
    return normalizedValues
}

第一步,我們設(shè)置了計算中需要使用到的 FFT 權(quán)重,它和我們要處理的數(shù)組大小相關(guān)。這些權(quán)重將在稍后實際的 FFT 計算中被使用到,它可以通過 vDSP_create_fftsetupD() 計算得到,并且對于給定大小的數(shù)組是可以重用的。因為在這里數(shù)組的大小是個恒定的常量,因此我們只需要計算一次權(quán)重,并將它作為全局變量并在每次 FFT 中重用即可。

在 FFT 方法中,我們初始化了一個用于存放操作結(jié)果的數(shù)組 fftMagnitudes,數(shù)組的初始元素都為 0,大小為之前正弦波的大小。FFT 運算的輸入?yún)?shù)都是實部加上虛部的復數(shù)形式,但我們真正關(guān)心的只是它的實數(shù)部分,因此我們初始化 splitComplexInput 的時候使用輸入數(shù)組作為實數(shù)部分,而將零作為虛數(shù)部分。然后 vDSP_fft_zipD()vDSP_zvmagsD() 負責執(zhí)行 FFT,并使用 fftMagnitudes 數(shù)組來存儲 FFT 從 FFT 中得到的結(jié)果的平方數(shù)。

在這里,我們使用了之前提到的基于 Accelerate 的 sqrt() 方法來計算平方根,返回實際大小,然后基于輸入數(shù)組的大小對值進行歸一化。

對一個單一的正弦波,以上所有操作的的結(jié)果如下:

http://wiki.jikexueyuan.com/project/objc/images/16-4.png" alt="" />

疊加的正弦波看起來像這樣:

http://wiki.jikexueyuan.com/project/objc/images/16-5.png" alt="" />

對這些值一個非常簡單的解釋是:這些結(jié)果表示了正弦波頻率的集合,從左邊開始,集合中的值表示了在該頻率下檢測到的波的振幅。它們關(guān)于中心對稱,因此你可以忽略圖中右半部分的值。

可以觀察到對于頻率為 4.0 振幅為 2.0 的波,在 FFT 中是一個 位于 4 對應(yīng)于 2.0 的值。同樣對于頻率為 1.0 振幅為 1.0 的波,在 FFT 中是位于 1 對應(yīng)值為 1.0 的點。盡管疊加后的正弦波得到的 FFT 波形比較復雜,但是依然能夠清晰地區(qū)分合并的兩個波在各自集合內(nèi)的振幅和頻率,就仿佛它們的 FFT 結(jié)果是分別被加入的一樣。

再次強調(diào),這是 FFT 運算的簡化版本,在上文的 FFT 代碼中有簡化操作,但關(guān)鍵是在 playground 中通過一步步創(chuàng)建方法,我們能輕松地探索一個復雜的信號處理操作,并且每一步操作的測試都能立即得到圖形反饋。

使用 Swift Playgrounds 快速創(chuàng)建原型的案例

我們希望這些例子能夠說明 Swift playground 在實踐新類庫和新概念上的作用。

上一個例子中的每一步里,我們都能在執(zhí)行時通過時間線中的圖案來觀察中間數(shù)組的狀態(tài)。這對于一個示例程序來說作用非常大,而且也以某種方式為程序提供了界面。所有這些圖像都實時更新,因此你能隨時返回到實現(xiàn)中并修改其中一個波的頻率或振幅,然后看著波形隨著處理步驟變化。這縮短了開發(fā)周期,并且對計算過程的體驗提供了巨大幫助。

這種立即反饋的交互式開發(fā)是為復雜的算法創(chuàng)建原型的很好的案例。在將這樣的復雜算法部署到實際的應(yīng)用之前,我們有機會在 playground 中對它進行驗證和研究。

上一篇:音頻 API 一覽下一篇:測試