web-dev-qa-db-ja.com

非エスケープパラメータをクロージャで使用すると、エスケープすることができる

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

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)でコンパイルされて動作しましたが、beta 6では動作しません。根本的な原因を教えてください。

98
Lukasz

これは、関数型のパラメータのデフォルト動作が変更されたためです。 Swift 3(特にXcode 8 Beta 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の提案 をご覧ください。

160
Hamish

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

1)@Hamishが彼の答えで指摘したように、結果を気にかけて本当にそれをエスケープしたいのであれば補完を@escapingとマークするだけです(それは単なる例としての@Lukaszの質問と非同期の可能性です)完了)

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

OR

2)結果を気にしない場合は、補完をオプションで結果をすべて破棄して、デフォルトの@noescapeの動作を維持します。たとえば、ユーザがすでに「立ち去った」ときに、呼び出し側のView Controllerが不注意なネットワーク呼び出しがあったからといってメモリ内でハングアップする必要はありません。私がここで答えを探しに来たときと同じようにサンプルコードはあまり関係ないので、@ noescapeをマーキングすることは最良の選択肢ではありませんでしたが、一見したところではそれが唯一のものとして聞こえました。

func fetchData(location: String, completion: ((DataFetchResult) -> Void)?) {
   ...
   completion?(self.result)
}
12
Vitalii