Edited at

autoclosureというマニアックで少し面白い機能

More than 3 years have passed since last update.


autoclosureとは

Swiftにはautoclosureという機能があります。

どういう機能かと言うと、下のように呼び出し側で渡した値をメソッド内ではクロージャーとして使えるものです。

method(1)

func method(@autoclosure closure: () -> Int) {
closure() // → 1
}

@autoclosureを使わない場合は下のように書く必要があります。

method({ return 1 })

func method(closure: () -> Int) {
closure() // → 1
}


autoclosureが使われている所

上の説明だけだと「これは何が嬉しいの??」という感じなので実際に使われているところを紹介します。

使われているところはSwiftの標準メソッドです。

具体例としては&&演算子があります。


&&演算子とautoclosure

Swiftでは&&演算子はグローバルな関数です。

public func &&<T : BooleanType, U : BooleanType>(lhs: T, @autoclosure rhs: () throws -> U) rethrows -> Bool

+-*/ など全ての演算子も同様です。

func +(lhs: Int, rhs: Int) -> Int

func -(lhs: Int, rhs: Int) -> Int
func *(lhs: Int, rhs: Int) -> Int
func /(lhs: Int, rhs: Int) -> Int

&&の両辺は引数なので、&&を普通に実装すると常にアプリがクラッシュします。

// autoclosureを使わない場合の&&の実装内容(予想)

func &&(left: Bool, right: Bool) -> Bool {
if !left { return false }
return right
}

let value: Int? = nil
// これは&&の1つ目の条件がtrue/falseに関わらず2つ目の条件は実行されるのでクラッシュする
let result = (value != nil && value! == 1)

これを回避する為には、&&の2番目の引数をクロージャーにして遅延実行する必要があります。

func &&(left: Bool, right: () -> Bool) -> Bool {

if !left { return false }
return right()
}

let value: Int? = nil
let result = (value != nil && { value! == 1 })

これで落ちなくはなりましたが、&&の右側に常に{}を付けるのはめんどくさいです。

しかし@autoclosureを使えば引数を自動でクロージャーに変換してくれるので{}を省略できてすっきりできます。

func &&(left: Bool, @autoclosure right: () -> Bool) -> Bool {

if !left { return false }
return right()
}

let value: Int? = nil
let result = (value != nil && value! == 1)


autoclosureの細かい仕様

autoclosureを使ったメソッドの引数の型はクロージャーの戻り値の型と等しくなります。

method(1) // → closureの戻り値がIntなので引数の型はInt型

func method(@autoclosure closure: () -> Int) {
print(closure()) // 1
}

クロージャーの引数はVoidにする必要があります。

// closureの引数を指定するとコンパイルエラー

func method(@autoclosure closure: (Int) -> Int) {
print(closure()) // 1
}


参考URL

Closures