L’utilisation de la fermeture d’un paramètre non-échappant peut lui permettre de s’échapper

J’ai un protocole:

enum DataFetchResult { case success(data: Data) case failure } protocol DataServiceType { func fetchData(location: Ssortingng, completion: (DataFetchResult) -> (Void)) func cachedData(location: Ssortingng) -> 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: Ssortingng) -> Data? { switch self.result { case .success(let data): return data default: return nil } } func fetchData(location: Ssortingng, 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 comstackr 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 utilisé dans Swift3 (Xcode8-beta5) mais ne fonctionne plus avec la bêta 6. Pouvez-vous m’indiquer la cause sous-jacente?

Cela est dû à une modification du comportement par défaut pour les parameters de fonction. Avant Swift 3 (en particulier la version livrée avec Xcode 8 beta 6), ils échapperaient par défaut – vous devriez les marquer comme @noescape afin de les empêcher d’être stockés ou capturés, garantissant ainsi qu’ils ne seront pas appelé après que la fonction se termine.

Cependant, maintenant, @noescape est la valeur par défaut – vous devez maintenant marquer les parameters de la fonction comme étant @escaping pour indiquer au compilateur qu’ils peuvent être stockés ou capturés.

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

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

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

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

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

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

OU

2) conservez le comportement par défaut de @noescape en rendant la complétion facultative en supprimant complètement les résultats lorsque vous ne vous souciez pas du résultat. Par exemple, lorsque l’utilisateur est déjà “parti” et que le contrôleur de la vue appelante n’a pas à se bloquer en mémoire simplement parce qu’il y a eu un appel réseau imprudent. Comme dans mon cas, lorsque je suis venu ici pour chercher une réponse et que le code d’échantillon n’était pas très pertinent pour moi, le fait de marquer @noescape n’était pas la meilleure option.

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