エスケープしないパラメーターをクロージャーで使用すると、エスケープできる可能性があります


139

私はプロトコルを持っています:

enum DataFetchResult {
    case success(data: Data)
    case failure
}

protocol DataServiceType {
    func fetchData(location: String, completion: (DataFetchResult) -> (Void))
    func cachedData(location: String) -> Data?
}

実装例:

    /// An implementation of DataServiceType protocol returning predefined results using arbitrary queue for asynchronyous mechanisms.
    /// Dedicated to be used in various tests (Unit Tests).
    class DataMockService: DataServiceType {

        var result      : DataFetchResult
        var async       : Bool = true
        var queue       : DispatchQueue = DispatchQueue.global(qos: .background)
        var cachedData  : Data? = nil

        init(result : DataFetchResult) {
            self.result = result
        }

        func cachedData(location: String) -> Data? {
            switch self.result {
            case .success(let data):
                return data
            default:
                return nil
            }
        }

        func fetchData(location: String, completion: (DataFetchResult) -> (Void)) {

            // Returning result on arbitrary queue should be tested,
            // so we can check if client can work with any (even worse) implementation:

            if async == true {
                queue.async { [weak self ] in
                    guard let weakSelf = self else { return }

                    // This line produces compiler error: 
                    // "Closure use of non-escaping parameter 'completion' may allow it to escape"
                    completion(weakSelf.result)
                }
            } else {
               completion(self.result)
            }
        }
    }

上記のコードはSwift3(Xcode8-beta5)でコンパイルおよび動作しましたが、ベータ6では動作しません。根本的な原因を教えてもらえますか?


5
これは、なぜSwift 3でそのように行われたのかについての非常に優れた記事です
Honey

1
これをしなければならないのは意味がありません。他の言語では必要ありません。
Andrew Koster、

回答:


243

これは、関数タイプのパラメーターのデフォルトの動作が変更されたためです。Swift 3(特にXcode 8ベータ6に同梱されているビルド)の前は、デフォルトでエスケープ@noescapeされます。保存またはキャプチャされないようにマークを付ける必要があります。これにより、存続期間が長続きしません。関数呼び出しの。

ただし、今@noescapeは関数型パラメーターのデフォルトです。このような関数を保存またはキャプチャする場合は、次のようにマークを付ける必要があります@escaping

protocol DataServiceType {
  func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void)
  func cachedData(location: String) -> Data?
}

func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) {
  // ...
}

この変更の詳細については、Swift Evolutionの提案を参照してください。


2
しかし、どのようにしてクロージャを使用して、エスケープできないようにしますか?
Eneko Alonso

6
@EnekoAlonso何を求めているのかよくわからない-エスケープされていない関数パラメーターを関数自体で直接呼び出すか、エスケープされていないクロージャーでキャプチャされたときに呼び出すことができます。この場合、非同期コードを扱っているため、async関数パラメーター(およびcompletion関数)がfetchData終了前に呼び出されるという保証はなく、したがってでなければなりません@escaping
Hamish 2016

プロトコルのメソッドシグネチャとして@escapingを指定する必要があると醜く感じます...それは私たちがすべきことですか?提案は言っていません!:S
Sajjon 2016

1
@Sajjon現在、@escapingプロトコル要件の@escapingパラメーターをその要件の実装のパラメーターと一致させる必要があります(非エスケープパラメーターの場合はその逆)。Swift 2でも同じでした@noescape
Hamish、


30

@noescapeがデフォルトであるため、エラーを修正する2つのオプションがあります。

1)@Hamishが彼の回答で指摘したように、結果を気にして本当にエスケープしたい場合は、完了を@escapingとしてマークするだけです(例として、ユニットテストを使用した@Lukaszの質問の例と非同期の可能性)。完了)

func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void)

または

2)結果を気にしない場合は、補完をオプションにして結果を完全に破棄することにより、デフォルトの@noescape動作を維持します。たとえば、ユーザーがすでに「立ち去った」場合で、不注意なネットワーク呼び出しがあったからといって、呼び出し側のビューコントローラがメモリ内でハングアップする必要はありません。私が回答を求めてここに来たときのように、サンプルコードは私にはあまり関係がなかったので、@ noescapeをマークすることは最良のオプションではありませんでした。

func fetchData(location: String, completion: ((DataFetchResult) -> Void)?) {
   ...
   completion?(self.result)
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.