[Swift 3.0] noescape ディレクティブの挙動がデフォルトになった話

2016.09.14

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

noescape とは

noescape は Swift 1.2 で導入された、関数オブジェクトのディレクティブです。@noescape を関数オブジェクトの直前につけると、関数オブジェクトが同期的に呼び出され、スコープ外にエスケープするタイミングで呼び出されないことを明示的にすることができます。

noescape ディレクティブの挙動の詳細は以下の記事で紹介しています。

[Swift][Objective-C] 同期的な関数ハンドラを示す noescape ディレクティブ

Swift 3.0 からは、noescape ディレクティブを付けた場合の挙動が、デフォルトとなりました。これはつまり、引数の関数オブジェクトは通常では同期的に呼び出されるということです。

いままで (Swift 3.0 以前)

HTTP リクエストなどのような非同期な処理は、関数オブジェクトをメソッドに引数で渡し、非同期ハンドラとすることが多いです。例えば以下の例では、someTask の中で someSyncHandler を呼び出し、そのハンドラの中で duplicateAndDoubled を呼び出しています。@noescape を付けているため、duplicateAndDoubled の呼び出しには self を明示的に書く必要がありません。duplicateAndDoubled メソッドの呼び出し時には、呼び出し元のクラスのインスタンスが必ず存在することが保証されています。

以下は、Swift 2.3 の記法で書いています。

class Hoge {

    func someSyncHandler(@noescape handler: Void -> Void) {
        handler()
    }
 
    func someTask() {
        someSyncHandler {
            // メソッドのスコープよりもハンドラ関数オブジェクトのライフサイクルは長くない
            // したがってインスタンスを関数オブジェクトにキャプチャしないため、
            // `self` を明示的にしなくてよい
            let array = duplicateAndDoubled(1)
            print(array)
        }
    }
    
    func duplicateAndDoubled(element: Int) -> [Int] {
        return [element * 2, element * 2]
    }
    
}

これから (Swift 3.0 以降)

Swift 3.0 からは @noescape を明示的に付けなくても、@noescape を付けたときと同じ挙動になります。デフォルトのままで、duplicateAndDoubled メソッドの呼び出し時に、呼び出し元のクラスのインスタンスが必ず存在することが保証されています。

以下は、Swift 3.0 の記法で書いています。

class Hoge {
    
    func someSyncHandler(handler: (Void) -> Void) {
        handler()
    }
    
    func someTask() {
        someSyncHandler {
            // メソッドのスコープよりもハンドラ関数オブジェクトのライフサイクルは長くない
            // したがってインスタンスを関数オブジェクトにキャプチャしないため、
            // `self` を明示的にしなくてよい
            let array = duplicateAndDoubled(1)
            print(array)
        }
    }
    
    func duplicateAndDoubled(_ element: Int) -> [Int] {
        return [element * 2, element * 2]
    }
    
}

escaping ディレクティブ

関数オブジェクトを非同期で処理する場合はどうするのかと言うと、新しく追加された escaping ディレクティブを明示的に付ける必要があります。

escaping ディレクティブを付けた場合は、渡した関数オブジェクトの中で self が関数オブジェクトにキャプチャされることを明示的にするため、self を付けなければいけません。

class Hoge {
    
    func someSyncHandler(handler: @escaping (Void) -> Void) {
        handler()
    }
    
    func someTask() {
        someSyncHandler {
            // self をキャプチャする必要がある
            let array = self.duplicateAndDoubled(1)
            print(array)
        }
    }
    
    func duplicateAndDoubled(_ element: Int) -> [Int] {
        return [element * 2, element * 2]
    }
    
}

ちなみに Foundation フレームワークの URLSessiondataTask:with:completionHandler: などのような非同期メソッドには @escaping が指定されています。

open func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> URLSessionDataTask

まとめ

端的にまとめると、次の通りです。

  • 同期的な処理のハンドラには @noescape は付ける必要はない
  • 非同期処理のハンドラには @escaping を付ける必要がある
  • @escaping が付いている関数オブジェクトの中では、スコープ外へのアクセスには値のキャプチャが必要

@escaping は非同期処理で必要となる、と覚えておけば良いでしょう。

参考