objective-cのblocksに変わるものとしてSwiftではモダンなクロージャが導入されています。
簡単な例から
var closure = {(valueMap:Dictionary<String,Int>) -> Int in
var total = 0
for p in valueMap.values {
total += p
}
return total
}
解説すると
dictionary を引数として Int を返すクロージャを宣言して 変数”closure”に格納しています。
なんだか構文がややこしいように見えますが”in”の前にパラメータ・戻り値、”in”の後に実装を書き、全体を{・・・}で囲っているだけです。
こんな構造
{ (引数) -> (戻り値) in
//実装
}
blocksに比べると随分と分かりやすい構文だと思います。
この変数に格納されたクロージャを使うときは
var populationDict:Dictionary<String,Int> = [
"kanazawa":10,
"komatsu":100000,
"nanao":50000
]
let population = closure(populationDict)
このように普通の関数呼び出しと同様に使えます。
呼び出し時にメソッドの型をコンパイラが分かっていたら
こんな感じでパラメータ、戻り値を省略してクロージャを記述できる
// クロージャを受け取る関数を宣言
func logCalcResult(calcFunc:(Double,Double) -> Double ,v1:Double,v2:Double) {
// v1,v2をパラメータとして受け取った関数(calcFunc)を実行
let calcResult = calcFunc(v1,v2)
NSLog("calcResult \(calcResult)")
}
// パラメータ、戻り値を省略したクロージャをインラインで宣言してます
// ※{・・・}の部分
// 引き渡されるパラメータ $0、$1、・・・ って名前で参照
logCalcResult({ return $0 + $1} ,20 ,30)
trailing closure って機能も使えます
関数の最後のパラメータがクロージャだった時、引数ブロックの外側にブロックを宣言してクロージャを渡せる
(説明がわかりにくい!)
// こんなclosureを受け取る関数があったとして
func logText(closure:() -> String){
NSLog(closure())
}
// 呼び出しの "()"の外側に { ... } を宣言してクロージャとして渡せる
logText(){
return "trailing クロージャ!!"
}
上手く使うとカッコ良くかけますね。
クロージャなので”Capturing Values”も行われます。
※要するにクロージャが宣言された”環境”への参照をクロージャが保持しちゃう機能
たとえばこんな感じでクロージャを返す関数を用意して
// 月から日までを列挙するクロージャを返す関数
func weekNameEnumBuilder() -> () -> String {
var weekday = -1
let names = ["月","火","水","木","金","土","日"]
return { () -> String in
//キャプチャしたweekdayをクロージャ内でインクリメント
weekday++
if (weekday == names.count) {
weekday = 0
}
return names[weekday]
}
}
このように実行するとweekdayがキャプチャされている事がわかりますね。
//キャプチャする値はweekNameEnumBuilderの実行時に個別に取得される。
//下記のように2回呼び出すとそれぞれ別の変数(weekday)をキャプチャしていることが分かる
let weekNamesA = weekNameEnumBuilder()
let weekNamesB = weekNameEnumBuilder()
NSLog("Capturing A-1: %@" , weekNamesA())
NSLog("Capturing A-2: %@" , weekNamesA())
NSLog("Capturing B-1: %@" , weekNamesB())
NSLog("Capturing B-2: %@" , weekNamesB())
NSLog("Capturing A-3: %@" , weekNamesA())
// Capturing A-1: 月
// Capturing A-2: 火
// Capturing B-1: 月
// Capturing B-2: 火
// Capturing A-3: 水
注意しなければならない事が1点。
クロージャー内でselfとかの参照をしてしまうと selfからクロージャーへの参照とクロージャーからselfへの参照が発生し、循環参照となってしまうというobjective-cではおなじみの現象がswiftでも発生します。
この問題への解決方法としてもobjective-cと同じように弱参照を行うことになるのですが、Swiftのクロージャではキャプチャリスト(Capture List)という形でクロージャ宣言の直前にキャプチャする対象の参照方式を指定することができます。
下記サンプルの [unowned self] ってのがそれで、selfをunownedで参照するように指定しています。
class Doraemon {
var poket = Dictionary<String,AnyObject>()
init(){
poket["タケコプター"] = "Takekoputer 9-inch,Late 2013"
}
// 引数にstringを受け取り、stringを返すクロージャを生成・返却する
func checkPocket() -> (String) -> String {
return {
// キャプチャリストは [参照方式 対象,参照方式 対象 ・・・] という形で記述する
// この例では self を unowned で参照
[unowned self] (itemname:String) -> String in
if self.poket[itemname] {
return "\(itemname)は持ってるよ。僕ドラえもん。"
} else {
return "\(itemname)は今修理中。"
}
}
}
}
let dora = Doraemon()
let checkPocket = dora.checkPocket()
NSLog(checkPocket("どこでもドア"))
NSLog(checkPocket("タケコプター"))
// どこでもドアは今修理中。
// タケコプターは持ってるよ。僕ドラえもん。
あと、クロージャや関数を変数に格納する場合が多々あると思います。
その宣言方法をメモっておきます。
// クロージャ、関数を変数に格納する場合の宣言
var funcA:(String) -> (String)
funcA = {
(a:String) -> String in
return "hello \(a)"
}
// 実行
funcA("マサオ")
// オプショナル(Optional)だった場合
// 全体をカッコで囲ってオプショナルの指定を行う
var funcB:((String) -> (String))?
funcB = {
(b:String) -> String in
return "hello \(b)"
}
// 実行する時はこんな感じ
funcB!("タテオ")
// 戻り値が無かった場合
var funcC:(String) -> ()
funcC = {
(c:String) in
println("hello \(c)")
}
funcC("ヒロシ")