前一章《函數式思想》中,咱們學習了函數式編程思想,即,經過構建一系列簡單、實用的函數,再「裝配」起來解決實際問題。本章藉助一個封裝 Core Image 的案例進一步練習函數式思想的應用。javascript
請帶着如下關鍵詞閱讀本文:java
本章案例是對 Core Image 中的 CIFilter
進行再次封裝。開始看到這一章時,我就有疑問:爲何會選擇這個不是很經常使用,但已經功能很是全面的 API 做爲封裝對象呢?後來想一想,封裝這種看上去沒什麼可封裝的 API 恰好可讓咱們體會到函數式思想的內涵,咱們甚至能夠不去關注函數的具體實現(下文中,我將會省略部分函數內部的具體實現,僅關注如何封裝高質量函數)。git
若是你不瞭解 CIFilter
的用途,能夠先來看看下面這段代碼和效果:github
let image = UIImage(named: "graynoel")
guard image != nil else { // 1
fatalError()
}
let originImage = CIImage(image: image!)
let filter = CIFilter(name: "CIGaussianBlur") // 2
guard filter != nil else { // 3
fatalError()
}
filter!.setValue(originImage, forKey: kCIInputImageKey) // 4
filter!.setValue(5.0, forKey: kCIInputRadiusKey) // 5
guard filter!.outputImage != nil else { // 6
fatalError()
}
let outputImage = UIImage(ciImage: filter!.outputImage!)複製代碼
代碼邏輯比較簡單:讀入一個 UIImage
對象,轉換爲 CIImage
實例,而後經過 Core Image 中的 CIFilter
API 對其進行「高斯模糊」處理。咱們不用去關心實現邏輯,只看看這段代碼自己的問題:編程
這些問題當咱們僅處理少許圖片時彷佛尚可接受,但大量使用時代碼就變得複雜了。swift
如何優化呢?很容易想到的解決辦法就是把須要的濾鏡變化封裝成函數,一個可能的函數封裝方式以下:api
func blur(originImage: CIImage, radius: Double) -> CIImage {
let outputImage: CIImage
...
return outputImage
}複製代碼
這樣作能夠達到效果,但是並無使用函數式思想,那函數式思想指導下的解決方案是什麼樣呢?ide
藉助前一章《函數式思想》的結論,首先應該選擇一個合適的類型,而合適的類型直接取決於所要實現功能的輸入和輸出,很明顯,輸入、輸出都是 CIImage
,那麼,咱們能夠獲得如下函數式定義:函數式編程
typealias Filter = (CIImage) -> CIImage複製代碼
若是您讀過前一章的內容,看到這句定義後,應該會有一種豁然開朗的感受。此時,咱們的目標發生了變化,再也不是爲了實現某一些具體的濾鏡方法,而是構建濾鏡相關的「工具庫」,爲一切高階函數封裝提供可能。函數
有了這個認識,咱們從新定義 blur
函數,並與上文中的函數進行對比:
// after
func blur(radius: Double) -> Filter {
return { image in
let outputImage: CIImage
...
return outputImage
}
}
// before
func blur(originImage: CIImage, radius: Double) -> CIImage {
let outputImage: CIImage
...
return outputImage
}複製代碼
基於這個思路,咱們能夠封裝不少相似的函數:
func colorGenerator(color: UIColor) -> Filter {
return { _ in
let c = CIColor(color: color)
let parameters = [kCIInputColorKey: c]
guard let filter = CIFilter(name: "CIConstantColorGenerator", withInputParameters: parameters) else {
fatalError()
}
guard let outputImage = filter.outputImage else { fatalError() }
return outputImage
}
}
func compositeSourceOver(overlay: CIImage) -> Filter {
return { image in
let parameters = [
kCIInputBackgroundImageKey: image,
kCIInputImageKey: overlay
]
guard let filter = CIFilter(name: "CISourceOverCompositing", withInputParameters: parameters) else {
fatalError()
}
guard let outputImage = filter.outputImage else { fatalError() }
let cropRect = image.extent
return outputImage.cropping(to: cropRect)
}
}複製代碼
獲得這個小型工具庫以後,咱們能夠很是便捷的「裝配」出複雜的濾鏡效果函數:
func colorOverlay(color: UIColor) -> Filter {
return { image in
let overlay = colorGenerator(color: color)(image)
return compositeSourceOver(overlay: overlay)(image)
}
}複製代碼
咱們來用一下試試:
let image = UIImage(named: "graynoel")
let originImage = CIImage(image: image!)!
// 高斯模糊
let radius = 5.0
let blurredImage = blur(radius: radius)(originImage)
// 對 blurredImage 再疊加顏色濾鏡、合成濾鏡
let overlayColor = UIColor.red.withAlphaComponent(0.2)
let outputImage = colorOverlay(color: overlayColor)(blurredImage)複製代碼
效果以下:
效果已經達成!爲了方便,對於上面這種將兩種濾鏡組合應用的狀況,能夠直接嵌套的來寫:
let outputImage = colorOverlay(color: overlayColor)(blur(radius: radius)(originImage))複製代碼
複雜的括號嵌套已經讓代碼失去了可讀性,如何優化呢?先來了解一個概念:柯里化(Currying),摘錄一段維基中的解釋:
In functional programming languages, and many others, it provides a way of automatically managing how arguments are passed to functions and exceptions.
簡單來講,就是把接受多個參數的方法進行一些變形,使其更加靈活的方法。咱們借用王巍(@onevcat)在其《100個Swift開發必備Tip》一書中柯里化 (CURRYING) 章節的例子進行介紹:
// 數字 +1 的函數
func addOne(num: Int) -> Int {
return num + 1
}複製代碼
若是咱們還須要 +二、+三、…… 的函數,這樣的寫法就沒法擴展了,所以能夠藉助柯里化,用以下方式進行定義:
func addNum(adder: Int) -> (Int) -> Int {
return { num in
return num + adder
}
}複製代碼
能夠看到,函數返回值變成了 (Int) -> Int
,這不正是一等函數的應用嗎!咱們再使用時,就能夠採用以下方法:
// 方式 1
let addTwo = addNum(adder: 2)
addTwo(1) // 3
// 方式 2
addNum(adder: 2)(3) // 5複製代碼
方式 1 中的 addTwo
就是咱們基於 addNum
函數「裝配」出的高階函數,而方式 2 的寫法則更簡潔。
讓咱們再回到濾鏡組合應用的問題,藉助柯里化,咱們定義如下函數:
func composeFilters(filter1: @escaping Filter, _ filter2: @escaping Filter) -> Filter {
return { image in
filter2(filter1(image))
}
}
// 調用
let composeTwoFilters = composeFilters(filter1: blur(radius: radius), colorOverlay(color: overlayColor))
let outputImage = composeTwoFilters(originImage)複製代碼
composeFilters
函數能夠將兩個 Filter
進行疊加,生成一個新的 Filter
,而後傳入 originImage
便可獲得最終結果。
原書章節還講解了引入運算符的方式進行代碼改寫,思路相似,本文再也不展開。
經過兩章的學習,咱們對高階函數(Higher-order function)已經有了一些瞭解,下面簡單總結一下。首先,仍是先看看維基對高階函數的定義:
In mathematics and computer science, a higher-order function (also functional, functional form or functor) is a function that does at least one of the following:
- takes one or more functions as arguments (i.e., procedural parameters),
- returns a function as its result.
高階函數知足兩個條件:
對比前文的代碼,composeFilters
函數就是一個高階函數,它以兩個 Filter
函數做爲參數,組合後的濾鏡函數做爲返回值。
高階函數應該與函數式思想相結合,函數式思想幫助咱們獲得一系列「小型」函數,而後「裝配」成複雜的高階函數,對功能進行擴展。一樣的,高階函數仍然能夠做爲「小型」函數,繼續「裝配」更高階的函數,這種「裝配」的便捷性和擴展性就得益於函數式編程方法。
柯里化是本章新引入的概念,經過前文代碼示例能夠看出,藉助柯里化,咱們能夠開發出用於「量產函數」的函數,就如同開發了一個函數的模板,能夠生成各類相似功能的函數,而且避免寫出不少重複代碼,也方便了後續維護。
可是,對於柯里化的應用是須要進行設計的,反覆嵌套或連接過多括號會嚴重影響代碼的可讀性,使用時,要根據具體問題適度柯里化,提升開發效率和代碼擴展性的同時,避免濫用致使的可讀性降低。
本文屬於《函數式 Swift》讀書筆記系列,同步更新於 huizhao.win,歡迎關注!