ネットで調べて出てきたSwiftコードを精読してみた

実装に困ったらstackoverflow先生に頼るのはよくあることですが、そのままコピペするのはいけません。「これってどうしてこういう実装になったの?」「stackoverflowからコピペしました!」だと怒られる気がします。*1そこで、今回は僕が参考にしたソースを精読して理解したいと思います。

想定されるレベル感はSwift書き始めたばかりくらいの人です。

今回の元ネタはこちらです。

ios - How do I resize the UIImage to reduce upload image size - Stack Overflow

画像をサイズ縮小するにはどうしたらいいか?という問いです。 最も+を得ている回答は以下のとおりです。

extension UIImage {
    func resized(withPercentage percentage: CGFloat) -> UIImage? {
        let canvasSize = CGSize(width: size.width * percentage, height: size.height * percentage)
        UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
        defer { UIGraphicsEndImageContext() }
        draw(in: CGRect(origin: .zero, size: canvasSize))
        return UIGraphicsGetImageFromCurrentImageContext()
    }
    func resized(toWidth width: CGFloat) -> UIImage? {
        let canvasSize = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))
        UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
        defer { UIGraphicsEndImageContext() }
        draw(in: CGRect(origin: .zero, size: canvasSize))
        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

(Xcode 8.2.1 • Swift 3.0.2)

では書いていきます。

1行目

extensionは型を拡張するときに使います。これをするとUIImageのインスタンスimageがあったときに、 imgae.resized(withPersentage: 0.1) で使えるようになります。

2行目

withPercentageは外部引数、percentageは内部引数です。 CGFloat型とFloat型の違いはなんだろう?と思って調べたところ、以下の通りだそうです。

Float型とDouble型はCocoaでも用途によって明確に使い分けられています。たとえば、画面上の座標に使用されるCocoaのCGFloat型は、32ビットのプラットフォームではFloat型、64ビットのプラットフォーム型ではDouble型になります。

どうしてこのような使い分けになっているかはちょっと分かりませんでした。 処理自体は、引数で指定したpercentage分だけ、画像の大きさを小さくするというものです。辺の長さが半分なら0.5を指定します。

3行目

CGSizeは縦横のサイズを指定するクラス。関連したものにCGRect、CGPointがあります。

4行目

UIGraphicsBeginImageContextWithOptionsはbitmap-basedなImageをOptionつきで作成できるクラス。scaleをこのクラスでは定義していないけど何故か動いている。気になってdefinitionにさかのぼったが、どうやらUIImageのopen varらしい。

5行目

deferは遅延実行です。

リソースの解法など、そのあとの実行フローの内容にかかわらず、スコープの退出時に確実に実行されて欲しい処理の記述に利用します。

で、何が実行されているかと言うとUIGraphicsEndImageContext()ですが、要するにUIGraphicsBeginImageContextWithOptionsの対になるもので、画像の描画を終わらせるために活用するものだと思います。開始と終了がワンセットだから、この位置でdeferされているのでしょうか。*2

6行目

draw(in: CGRect)は、元々のUIImageを引数で指定した大きさに変換して再描画する処理です。CGRect()では、座標とサイズの設定が行われています。origin: .zeroの方は、座標を表しているから、元のところから動かさないの意味?

7行目

UIGraphicsGetImageFromCurrentImageContext()は、現在の"Context"にあるImageを取得するというものです。 つまり、4行目~7行目でUIGraphicsBeginImageContextWithOptions→UIGraphicsGetImageFromCurrentImageContext→UIGraphicsEndImageContextの流れが記述されているということになります。

8行目以降

大きさの設定方法以外全部同じなので省略

まとめ

振り返ってみるとそこまで難しい処理をやっているわけではなく、Contextの生成→Imageを作成→Contextの削除という流れがあるとわかりました。一つでもちゃんと読んで理解できると、他の類似のソースを見たときに何が違っているのか理解しやすくなりますね。

参考

石川・西山『改訂新版 Swift実践入門』

https://developer.apple.com/documentation/uikit/1623912-uigraphicsbeginimagecontextwitho?language=objc

https://developer.apple.com/documentation/uikit/1623933-uigraphicsendimagecontext

https://developer.apple.com/documentation/uikit/uiimage/1624092-draw

https://developer.apple.com/documentation/coregraphics/cgrect/1454856-init

*1:社畜ちゃん』の後輩ちゃんもどっかで怒られていました

*2:他の例を見ると、deferを使わずにreturnの直前に置いているのもあるので、活用は好みの問題なのかなとも思いました。