Swift

Swift/クロージャ

クロージャの概要

{
  (仮引数: 型) -> 型 in
    文...
}

仮引数には var, let, inout の修飾を付けることができ、可変長引数も利用できる。 ただし、引数に対してデフォルト値を指定することはできない。 型宣言の後、実行する文を記述する前に in というキーワードが必要である。

var c1 = { () -> () in print("Hello") }
c1()     // Helloと表示される

クロージャのインスタンスが格納された変数に実引数列を適用すると実行できる。 文の先頭にクロージャ式を書いて、直後に実引数列を適用するという書き方はできない。 クロージャを評価した結果を変数に代入するような書き方は可能である。

{ () -> () in print("Hello") }()     // エラー
var pi:Double = { () -> Double in atan(1.0) * 4.0 }()

文が複数ある場合、値を返すクロージャならば、ブロックの中で return 文を実行しなければならない。

let c2 = { (a:Int, b:Int) -> Double in
  if b == 0 { return 0.0 }
  return Double(a) / Double(b)
}
print(c2(10,8))

クロージャの仮引数と返り値の型宣言

仮引数と返り値の型の宣言を省略することも可能であるが、関数のように「返り値がない場合に ->型 の部分が省略できる」状況とは異る。 クロージャの構造および使われている状況から返り値の型が推測できる場合に省略が可能である。

  • ブロック内に文がひとつしか存在しないとき
    var c1 = { () -> () in print("Hello") }    // 省略なし
    var c1 = { () -> Void in print("Hello") }    // 省略なし(()と Voidは同じ)
    var c1 = { () in print("Hello") }    // 返り値の型を省略
    var c1 = { print("Hello") }    // 引数も省略
    var pi:Double = { () -> Double in atan(1.0)*4.0 }()
    var pi = { atan(1.0)*4.0 }()  // 変数piの型宣言も省略できる
  • クロージャが使われる状況から、返り値の型が推測できる場合 (Swiftではreturn の値を調べてクロージャの返す値を推測することはしていないので注意すること)
    var c3: Int = {  // Int型を返すと推測される
      print("What?")
      return Int(c2(9,4))
    }()              // 直ちに評価する

関数とクロージャの型

関数とクロージャの型は次の通り。

(引数1の型, 引数2の型, ...) -> 返り値の型

返り値がない場合は、返り値の型として Void または () と記述する。

let c1 = { (a:Int, b:Int) -> Double in return Double(a) / Double(b) }
var c2: (Int, Int) -> Double = c1
c2(10,4)     // 結果は 2.5
print(c2)    // "(Function)"と表示される
func f1(a:Int, _ b:Int) -> Double { return Double(a) / Double(b) }
c2 = f1;
c2(10,4);    // 結果は2.5

上の例で、関数とクロージャはを区別する必要はなく、「(Int,Int)->Double」 という 同一の型であることがわかります。

引数が1個の場合は、引数を囲む括弧を省略できる。

 引数の型 -> 返り値の型
func f2(a:Int) -> Double { return Double(a) / 100.0 }
let c3: (Int) -> Double = f2
let c4: Int -> Double = f2    // 引数を囲む括弧を省略

変数のキャプチャ

クロージャのインスタンスが生成される際、クロージャの外側にある変数の値を取り込んで(キャプチャして)インスタンスの一部とし、インスタンスが呼び出される時にはいつでも値を取り出して使うことができる。

  • グローバル変数は、クロージャの内部から変数自体に直接アクセスできる。値の変更もできる。
  • ローカル変数は、ローカル変数として存在している間は共通の値が参照可能で、更新もできる。
    • 同じコードブロックでも一旦実行が終わり、再び実行したときのローカル変数は以前のローカル変数とは別物なので、値を共有することはできない。
    • あるコードブロック内で複数個のクロージャが同時に生成され、それらが同じローカル変数を参照すると、変数の値は共有される。コードブロックの実行が終わり、元のローカル変数が消滅した後でも、クロージャ間で値の共有ができる。

クロージャが個別にローカル変数をキャプチャする例

var globalCount = 0
func maker(a:Int, _ b:Int) -> () -> Int {
  var localvar = a
  return { () -> Int in   // クロージャを返す。クロージャは実引数なしで呼ばれてIntを返す
    globalCount++         // グローバル変数は参照されるだけ
    localvar += b         // localvar, bはキャプチャされる
    return localvar
  }
}
var m1 = maker(10,1)   // クロージャを生成する
var m2 = maker(20,5)   // クロージャを生成する
m1()          // 11
m2()          // 25
m1()          // 12

複数のクロージャが同じローカル変数をキャプチャする例

var m1:(() -> ()) ! = nil    // m1は仮引数無しで呼び出されて値を返さないクロージャを保持するが、今はnil
var m2:(() -> ()) ! = nil    // m2も同様
func makerW(a:Int) {
  var localvar = a
  m1 = { localvar += 1: print("m1: \(localvar)") }
  m2 = { localvar += 5: print("m2: \(localvar)") }
}
makerW(10)     // クロージャを生成する
m1()           // 11
m2()           // 16
m1()            // 17

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2016-05-25 (水) 14:50:57 (1536d)