Functional Swift 3章 "Wrapping Core Image" まとめ

この章ではCore ImageをfunctionalにラッピングするAPIを構築することで、より実戦的に高階関数と関数合成を利用する方法を学びます。

Filter型

typealias Filter = CIImage -> CIImage

オリジナルの let filter = CIFilter(name: "CIVignette") などのkey値で初期化するCIFilterをカプセル化するためにFilter型を関数として定義。

フィルタの構築

Filter型を定義したので、個別のFilterを定義していく

全て func myFilter("パラメータ") -> Filter の形

Blur

func blur(radius: Double) -> Filter {
        return { image in
            let parameters = [
                kCIInputRadiusKey: radius,
                kCIInputImageKey: image
            ]
            guard let filter = CIFilter(name: "CIGaussianBlur", withInputParameters: parameters) else { fatalError() }
            guard let outputImage = filter.outputImage else { fatalError() }
            return outputImage
        }
    }

引数はぼかしの半径のみです。 CIImage型のimageをとり、新しいCIImageを返す関数(先ほど定義したFilter(CIImage -> CIImage))を返します。 同じパターンで様々なフィルタ関数を定義できます。

Chrome

func chrome() -> Filter {
        return { image in
            let parameters = [
                kCIInputImageKey: image
            ]
            guard let filter = CIFilter(name: "CIPhotoEffectChrome", withInputParameters: parameters) else { fatalError() }
            guard let outputImage = filter.outputImage else { fatalError() }
            return outputImage
        }
    }

*1

フィルタを合成

ここまで作成したぼかしとクロームのフィルタ関数を合成していきます。

let url = NSURL(string: "http://www.objc.io/images/covers/16.jpg")
let image = CIImage(contentsOfURL: url!)!
        
let blurredImage = blur(5.0)(image)
let chromedImage = chrome()(blurredImage)

一度ブラーをかけたimageに新たにクロームフィルタをかけて画像を生成しています。 もちろん一つにまとめることもできますが、括弧が増え途端に読めなくなってしまいます。

let result = chrome()(blur(5.0)(image))

そこで、カスタムオペレーションを定義することで読みやすさを保つことができます。

infix operator >>> { associativity left }

func >>>(filter1: Filter, filter2: Filter) -> Filter {
    return { image in filter2(filter1(image)) }
}


let filter = chrome() >>> blur(5.0)
let result = filter(image)

高階関数、関数合成を使用することによって、安全性、モジュール性、明瞭性を保ったAPIをデザインすることができます。

*1:本書では別のフィルタ関数を使っていますが、複雑になるので引数なしのChromeに変更