60
60

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Swift] Closureの書き方いろいろ

Last updated at Posted at 2015-02-26

これはなに

Swift には "Closure" という無名関数があります。Objective-C では "Blocks" と言われていたものです。
Closure は柔軟な書き方ができます。少しまとめてみました。
なお環境は Xcode6.1.1(Swift1.1), Playground です。

  • いろいろな書き方
    • 基本形
    • 一行だけの形
    • 短縮形
  • Operator Functions
  • Tailing Closure
  • Capturing Values
  • 参考

いろいろな書き方

基本形

{ (parameters) -> returnType in
    statements
}

これが公式リファレンスに書かれている形です。
パラメータがタプルで渡ってきて、返り値の型を指定して、 in と書きます。
あとは任意の処理を書いて終わりです。
なので、以下のように書くことができます。

{ (p1: Int, p2: Int) -> Int in
    return p1 + p2
}

p1, p2 の和を返すクロージャです。
実際に使うには、このクロージャに何か名前をつけてあげてパラメータを渡すということになります。

let and = { (p1: Int, p2: Int) -> Int in
    return p1 + p2
}

and(1, 2)   // 3
and(0, 0)   // 0

また、型推論が使えるので、パラメータの型と返り値の型がわかっているときは省略できます。

let and: (Int, Int) -> Int = { (p1, p2) in
    return p1 + p2
}

(Int, Int) -> Intand の型です。左辺がパラメータの型で右辺が返り値の型になっています。
またパラメータはタプルじゃなくてもいいし、クロージャを一行で書いてもいいです。

let and: (Int, Int) -> Int = { p1, p2 in return p1 + p2 }

一行だけの形

クロージャの文が一行だけの場合は、return を省略することができます。

let and: (Int, Int) -> Int = { p1, p2 in p1 + p2 }

型を宣言するところのカッコは省略できません。

短縮形

さらに、与えられたパラメータは先頭から $0, $1, $2, ... と書くことができます。

let and: (Int, Int) -> Int = { $0 + $1 }

閑話休題

この例を見ると「普通の関数でいいじゃん」と思えてしまうのでおそらく例が悪いのですが、
なぜクロージャを使うかというと、「関数を引数として渡す」とか「オブジェクトのように扱う」とかいうことをしたい場面があるからです。
要は C言語 の関数ポインタだと思っているのですが、関数ポインタがわかっている人はこの記事を読んでも面白くないと思うのであまり説明になっていない気がします。

Operator Functions

さらに短く書く方法です。
演算子の性質(与えられたパラメータと返り値)がわかっていれば、その演算子だけを書いてクロージャとすることができます。
ただし演算子だけ書くことになるので、前述の記法だと使うことができないので以下のように add を関数化してみます。

func add(p1: Int, p2: Int, closure: (Int, Int) -> Int) -> Int {
    return closure(p1, p2)
}

これを先ほどのように使うと、

add(1, 2, { $0 + $1 })   // 3

となります。ここで + 演算子は (Int, Int) を受け取り Int を返すので Operator Functions を使うとさらに短くなります。

add(1, 2, +)             // 3

演算子自体を関数とみているんですね。
まあここまでくると可読性がどうなのか…実践で使えるのか疑問ではあります。
Swift は演算子オーバーロードがあるので汎用的に使おうと思えば使えますが…うーん…

Tailing Closure

さてここからはクール(だと思う)にクロージャを書きます。
関数の引数の最後にクロージャがある場合、クロージャだけを外に出すことができます。

add(1, 2, { p1, p2 in
    return p1 + p2
})

これを

add(1, 2) {
    p1, p2 in
    return p1 + p2
}

こう書けます。
中にクロージャがあって長く見える関数をスッキリ書くことができました。

引数の最後のクロージャだけなので、二つ以上クロージャを引数にとる関数では最後以外は普通に書かないといけません。

Capturing Values

ある処理を何度もして、値の保存をしておきたい場合などに使います。Lispの継続に近い感じがしています。
Swift では関数の中に関数を書くことができます。関数が関数を返すという仕組みにすることでそれを実現できます。
実際にコードを見た方が早いですね。

func addNumber(number: Int) -> () -> Int {
    var sum = 0
    func add() -> Int {
        sum += number
        return sum
    }
    return add
}

let addTen = addNumber(10)
addTen()                    // 10
addTen()                    // 20
addTen()                    // 30
let addSeven = addNumber(7)
addSeven()                  // 7
addSeven()                  // 14
addSeven()                  // 21
addTen()                    // 40

とても頼もしいですね。
addNumber(number) は、実行するごとに足したい数字を引数にとって ()->Int という関数を返します。
その関数内の変数は保たれているので、関数を呼び出すごとに合計値が増えていきます。

繰り返し使いたい部分などを Capturing Values で書くと余分なグローバル変数が増えなくてよいのではないでしょうか。

まとめ

  • クロージャにはさまざまな書き方がある
  • 極限まで短く書けるが現実的には可読性との兼ね合いになりそう
  • Tailing Closure を使って美しく書きたい
  • Capturing Values は関数型っぽさがあり魅力的である

参考

https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html
http://fuckingswiftblocksyntax.com
http://qiita.com/edo_m18/items/1d93af7de75c6d415f19

60
60
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
60
60

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?