Error de Swift 3.0: los cierres de escape solo pueden capturar en parámetros explícitamente por valor

Estoy intentando actualizar mi proyecto a Swift 3.0, pero tengo algunas dificultades. Me está llegando el siguiente error: “Los cierres de escape solo pueden capturar los parámetros explícitamente por valor”.

El problema está dentro de esta función:

fileprivate func collectAllAvailable(_ storage: inout [T], nextUrl: String, completion: @escaping CollectAllAvailableCompletion) { if let client = self.client { let _ : T? = client.collectionItems(nextUrl) { (resultCollection, error) -> Void in guard error == nil else { completion(nil, error) return } guard let resultCollection = resultCollection, let results = resultCollection.results else { completion(nil, NSError.unhandledError(ResultCollection.self)) return } storage += results // Error: Escaping closures can only capture inout parameters explicitly by value if let nextUrlItr = resultCollection.links?.url(self.nextResourse) { self.collectAllAvailable(&storage, nextUrl: nextUrlItr, completion: completion) // Error: Escaping closures can only capture inout parameters explicitly by value } else { completion(storage, nil) // Error: Escaping closures can only capture inout parameters explicitly by value } } } else { completion(nil, NSError.unhandledError(ResultCollection.self)) } } 

¿Alguien puede ayudarme a arreglar eso?

Usar un parámetro inout exclusivamente para una tarea asíncrona es un abuso de inout , como cuando se llama a la función, el valor de la persona que llama que se pasa al parámetro inout no cambiará.

Esto se debe a que inout no es una referencia de paso a paso, es solo una copia instantánea mutable del parámetro que se escribe al llamador cuando la función sale, y como una función asíncrona sale inmediatamente, no se escribirán cambios.

Puede ver esto en el siguiente ejemplo de Swift 2, donde se permite capturar un parámetro inout mediante un cierre de escape:

 func foo(inout val: String, completion: (String) -> Void) { dispatch_async(dispatch_get_main_queue()) { val += "foo" completion(val) } } 

 var str = "bar" foo(&str) { print($0) // barfoo print(str) // bar } print(str) // bar 

Debido a que el cierre que se pasa a dispatch_async escapa al tiempo de vida de la función foo , los cambios que realice en val no se escriben de nuevo en la str la persona que llama – el cambio solo se puede pasar de la función de finalización.

En Swift 3, ya no se permite capturar @escaping mediante los cierres de @escaping , lo que elimina la confusión de esperar una referencia de paso a paso. En su lugar, debe capturar el parámetro copiándolo , agregándolo a la lista de captura del cierre:

 func foo(val: inout String, completion: @escaping (String) -> Void) { DispatchQueue.main.async {[val] in // copies val var val = val // mutable copy of val val += "foo" completion(val) } // mutate val here, otherwise there's no point in it being inout } 

( Editar: desde publicar esta respuesta, los parámetros inout ahora se pueden comstackr como una referencia paso a paso, que se puede ver mirando el SIL o IR emitido. Sin embargo, no puede tratarlos como tales debido a que hay no hay garantía de que el valor de la persona que llama seguirá siendo válido después de la llamada a la función).


Sin embargo, en su caso simplemente no hay necesidad de una inout . Solo necesita agregar la matriz resultante de su solicitud a la matriz actual de resultados que pasa a cada solicitud.

Por ejemplo:

 fileprivate func collectAllAvailable(_ storage: [T], nextUrl: String, completion: @escaping CollectAllAvailableCompletion) { if let client = self.client { let _ : T? = client.collectionItems(nextUrl) { (resultCollection, error) -> Void in guard error == nil else { completion(nil, error) return } guard let resultCollection = resultCollection, let results = resultCollection.results else { completion(nil, NSError.unhandledError(ResultCollection.self)) return } let storage = storage + results // copy storage, with results appended onto it. if let nextUrlItr = resultCollection.links?.url(self.nextResourse) { self.collectAllAvailable(storage, nextUrl: nextUrlItr, completion: completion) } else { completion(storage, nil) } } } else { completion(nil, NSError.unhandledError(ResultCollection.self)) } } 

Si desea modificar una variable pasada por referencia en un cierre de escape, puede usar KeyPath. Aquí hay un ejemplo:

 class MyClass { var num = 1 func asyncIncrement(_ keyPath: WritableKeyPath) { DispatchQueue.main.async { // Use weak to avoid retain cycle [weak self] in self?[keyPath: keyPath] += 1 } } } 

Puedes ver el ejemplo completo aquí .