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

Playground 快速原型制作

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

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

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

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

將 Playground 用于文檔和測試

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

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

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

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

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

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

然后想試試 filter() 方法:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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,我們的初始數組大小必須是 2 的冪次方。把 sineArraySize 值改為像 32,128 或 256 將改變之后顯示的圖像的密度,但它不會改變計算的基本結果。

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

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="" />

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

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 數組中的元素依次合并為一個數組。在運算中創(chuàng)建了一個和輸入的數組長度相等的空白數組(假設輸入的兩個數組長度相等)。由于 Swift 的一維數組可以直接映射成 C 語言的數組,因此我們只需要將作為參數的 Doubles 類型數組直接傳遞給 vDSP_vaddD() 方法,并在我們的數組結果前加前綴 &。

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

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="" />

果然,結果是一致的。

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

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

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

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

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

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

有了這個以后,我們來實現 FFT。我們并不打算在 FFT 設置的細節(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
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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