Swift で Optional で包んだクロージャーを扱う場面
コラム
そういえば Optional で包んだクロージャーも糖衣構文で簡単に扱えるのかなと思って確かめてみました。
そこから、コールバックとして使うクロージャーの在り方についても考えてみます。
岸川さんのSwiftで使いやすいAPIを書くために気をつけていること - 24/7 twenty-four seven を拝見して、この中の『省略された場合でも呼び出される方でクロージャがnilかどうかをチェックする必要がない』というところに興味が湧きました。
クロージャーを Optional で包んだとしたら、なんらかの形で nil チェックを行うことが必須になるのは確かとして、そういえば Optional なクロージャーを糖衣構文で扱ったときにどんな風になるのか意識したことがなかったので確かめてみました。
Optional で包んだクロージャーの扱い
まず、次のクロージャーを定義してみます。
let predicate:((Int)->Int)? = { $0 + 1 }
それか次のような関数にクロージャーが渡されるか渡されないか分からない場面の方がイメージしやすいかもしれません。
func myFunction(predicate:((Int)->Int)?) {
}
どちらにしても、整数型を受け取って整数型の結果を返すクロージャーを意味する (Int)->Int 型を Optional に包んだ形で、変数predicate を定義しています。
このクロージャーに対して Swift の Optional 関連の糖衣構文を使ってみます。
Optional Binding
まず初めに思いつくのが Optional Binding です。
if let predicate = predicate {
result = predicate(value)
}
こうすると、変数predicate にクロージャーが与えられたときに、それを取り出して普通に実行するというコードになります。
普通に nil チェックしている感じになれて、いちばん自然に書けた気がするコードに思います。
Optional Chaining
Optional といえば「?」を使って nil ではなかった場合だけに処理を継続するOptional Chaining という構文があります。
let result:Int? = predicate?(value)
これはクロージャーでも普通に使えるので、渡されるか渡されるかも分からない、最後に呼び出すようなコールバックにはちょうど扱いやすいようにも感じました。
このとき、普段の Optional Chaining と同様に、戻り値は、本来の型が Optional でラップされた形になるところだけ注意です。戻り値が Void のクロージャーであれば気にしなくて良いので、自然に呼び出せると思います。
この書き方なら、慣れてしまえば1行で『クロージャーがあったら実行する』という意味を汲み取ることができます。
クロージャーを受け取る時にも Optional なら『渡すかは任意』という意味が込められるので、可読性の面でも理屈の面でも、これがいちばんスマートなように思えました。
func action( callback:( (NSError?) -> Void )? ) {
var error:NSError?
:
callback?(error)
}
action { error in ... }
action(callback: nil)
この例で callback に nil を渡すのを明記しないといけないのが気になるときは、引数の既定値として = nil を指定しておくのも良いかもしれません。そうすると callback が不要なときには action() と記載できます。
強制アンラップ
わざわざ Optional にしておいて強制アンラップというのも変な気がしますが、もちろんそれも普通に書けます。
let result:Int = predicate!(value)
戻り値も本来の型で得られるので自然に使えるのですけど、これを使う場面というのは、そもそもクロージャーが Optional で包まれている必要があるのか、そこから見直した方が良さそうです。
必要になる場面としては『関数が戻り値としてクロージャーを返す場合で、仕様では nil が返ってくるかもしれないことになっているが、今回の呼び出し方では nil が返ってくることはあり得ない』ときに、そんな意思表示を込めて使う場面に限られそうです。