web-dev-qa-db-fra.com

L'utilisation du paramètre non-d'échappement par la fermeture peut lui permettre de s'échapper

J'ai un protocole:

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

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

Avec un exemple d'implémentation:

    /// 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)
            }
        }
    }

Le code ci-dessus compilé et travaillé dans Swift3 (Xcode8-beta5) mais ne fonctionne plus avec la version 6. Pouvez-vous m'indiquer la cause sous-jacente?

100
Lukasz

Cela est dû à une modification du comportement par défaut des paramètres de type de fonction. Avant Swift 3 (en particulier la version livrée avec Xcode 8 beta 6), ils s’échappaient par défaut. Vous devez les marquer @noescape pour les empêcher d’être stockés ou stockés. capturés, ce qui garantit qu'ils ne survivront pas à la durée de l'appel de fonction.

Cependant, maintenant, @noescape est la valeur par défaut pour les paramètres de type fonction. Si vous souhaitez stocker ou capturer de telles fonctions, vous devez maintenant les marquer @escaping:

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

Voir le proposition de Swift Evolution pour plus d’informations sur ce changement.

163
Hamish

Comme @noescape est la valeur par défaut, il existe 2 options pour corriger l'erreur:

1) comme l'a souligné @Hamish dans sa réponse, marquez simplement l'achèvement comme @ s'échappant si vous vous souciez du résultat et que vous voulez vraiment qu'il s'échappe (c'est probablement le cas dans la question de @ Lukasz avec les tests unitaires comme exemple et possibilité asynchrone achèvement)

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

OR

2) conservez le comportement @noescape par défaut en rendant l’achèvement facultatif, en supprimant les résultats dans les cas où vous ne tenez pas compte du résultat. Par exemple, lorsque l'utilisateur est déjà "parti" et que le contrôleur de vue appelant n'a pas besoin de rester en mémoire simplement parce qu'il y a eu un appel réseau imprudent. Comme ce fut le cas dans mon cas lorsque je suis venu ici pour chercher une réponse et que l'exemple de code n'était pas très pertinent pour moi, marquer @noescape n'était pas la meilleure option, même si cela semblait être la seule à première vue.

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