Swift 3.0 Error: los cierres de escape solo pueden capturar parameters inout explícitamente por valor

Estoy intentando actualizar mi proyecto a Swift 3.0, pero tengo algunas dificultades. Recibo el siguiente error: "Escaping closures solo puede capturar parameters inout explícitamente por valor".

El problema está dentro de esta function:

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 me puede ayudar a arreglar eso?

El uso de un parámetro inout exclusivamente para una tarea asíncrona es un abuso de inout , ya que al llamar a la function, el valor de la persona que llama que se pasa al parámetro inout no se cambiará.

Esto se debe a que inout no es un paso por reference, es solo una sombra instantánea mutable del parámetro que se escribe de nuevo en la persona que llama cuando sale la function, y debido a que una function asíncrona sale inmediatamente, ningún cambio se volverá a escribir.

Puede ver esto en el siguiente ejemplo de Swift 2, donde se permite que un parámetro inout sea ​​capturado por 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 a la duración de la function foo , cualquier cambio que haga a val no se devuelve a la str la persona que llama; el cambio solo se puede observar al pasar a la function de finalización.

En Swift 3, los parameters de input ya no pueden ser capturados por @escaping closures, lo que elimina la confusión de esperar una reference de paso por paso. En su lugar, debe capturar el parámetro copiándolo , agregándolo a la list 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 } 

( Edición: desde la publicación de esta respuesta, los parameters de input pueden ahora comstackrse como una reference de paso por paso, que puede verse mirando el SIL o el IR emitido. Sin embargo, no puede tratarlos como tal debido al hecho de que hay no hay garantía alguna de que el valor de la persona que llama permanezca válido después de la llamada a la function).


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

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