187 votes

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?

321voto

Hamish Points 42073

Cela est dû à un changement dans le comportement par défaut pour les paramètres de la fonction. Avant Swift 3 (plus précisément de la version fourni avec Xcode beta 8 6), ils seraient en défaut de s'échapper – vous devez marquer comme @noescape afin de les empêcher d'être stockées ou capturés, ce qui vous garantit de ne pas être appelée après la fonction se termine.

Cependant, maintenant, @noescape est la valeur par défaut – maintenant, vous devez marquer les paramètres de la fonction en tant que @escaping pour indiquer au compilateur qu'ils peuvent être stockés ou capturés.

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

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

Voir la fulgurante Évolution de proposition pour en savoir plus sur ce changement.

46voto

Depuis @noescape est la valeur par défaut, il y a 2 options pour corriger l'erreur:

1) @Hamish a souligné, dans sa réponse, il suffit de marquer l'achèvement @échapper si vous ne vous souciez du résultat et je veux vraiment qu'il puisse s'échapper (c'est probablement le cas dans @Lukasz de la question avec les Tests Unitaires à titre d'exemple et possibilité de async achèvement)

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

OU

2) conservez la valeur par défaut @noescape comportement en faisant l'achèvement en option rejeter les résultats dans l'ensemble dans le cas où vous n'avez pas de soins sur le résultat. Par exemple lorsque l'utilisateur a déjà "reparti" et l'appel de la vue-contrôleur n'a pas à accrocher dans la mémoire juste parce qu'il y a certains négligent d'appel du réseau. Comme elle l'a été dans mon cas quand je suis venu ici à la recherche de la réponse et de l'exemple de code n'était pas très pertinent pour moi, si le marquage @noescape n'était pas la meilleure option, d'événement, ça sonnait comme le seul du premier coup d'œil.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X