asynchronous - how - script async defer
¿Cómo ejecuto devoluciones de llamada asíncronas en el patio de recreo? (8)
Muchos métodos CocoaTouch y CocoaTouch tienen callbacks de finalización implementados como bloques en Objective-C y Closures en Swift. Sin embargo, al probar esto en Playground, nunca se llama a la finalización. Por ejemplo:
// Playground - noun: a place where people can play
import Cocoa
import XCPlayground
let url = NSURL(string: "http://stackoverflow.com")
let request = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue:NSOperationQueue.currentQueue() {
response, maybeData, error in
// This block never gets called?
if let data = maybeData {
let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
println(contents)
} else {
println(error.localizedDescription)
}
}
Puedo ver la salida de la consola en mi línea de tiempo de Playground, pero la println
de println
en mi bloque de finalización nunca se llama ...
A partir de XCode 7.1, XCPSetExecutionShouldContinueIndefinitely()
está en desuso. La forma correcta de hacerlo ahora es solicitar primero la ejecución indefinida como una propiedad de la página actual:
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
... luego indica cuando la ejecución ha terminado con:
XCPlaygroundPage.currentPage.finishExecution()
Por ejemplo:
import Foundation
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://.com")!) {
result in
print("Got result: /(result)")
XCPlaygroundPage.currentPage.finishExecution()
}.resume()
Esta API cambió nuevamente en Xcode 8 y se movió a PlaygroundSupport
:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
Este cambio se mencionó en la sesión 213 en la WWDC 2016 .
La razón por la que no se llaman las devoluciones de llamada es porque RunLoop no se está ejecutando en el patio de recreo (o en el modo REPL para el caso).
Una forma un tanto ingeniosa pero efectiva de hacer que las devoluciones de llamada funcionen es con un indicador y luego iterando manualmente en el runloop:
// Playground - noun: a place where people can play
import Cocoa
import XCPlayground
let url = NSURL(string: "http://.com")
let request = NSURLRequest(URL: url)
var waiting = true
NSURLConnection.sendAsynchronousRequest(request, queue:NSOperationQueue.currentQueue() {
response, maybeData, error in
waiting = false
if let data = maybeData {
let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
println(contents)
} else {
println(error.localizedDescription)
}
}
while(waiting) {
NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate())
usleep(10)
}
Este patrón a menudo se ha utilizado en pruebas unitarias que necesitan probar devoluciones de llamada asíncronas, por ejemplo: Patrón para la prueba unitaria de la cola asíncrona que llama a la cola principal al finalizar
Las nuevas API como XCode8, Swift3 e iOS 10 son,
// import the module
import PlaygroundSupport
// write this at the beginning
PlaygroundPage.current.needsIndefiniteExecution = true
// To finish execution
PlaygroundPage.current.finishExecution()
Si bien puede ejecutar un bucle de ejecución manualmente (o, para un código asíncrono que no requiere un bucle de ejecución, usar otros métodos de espera como semáforos de despacho), la forma "incorporada" que proporcionamos en áreas de juego para esperar el trabajo asíncrono es importe el marco XCPlayground
y establezca XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
. Si se ha establecido esta propiedad, cuando finalice su fuente de juegos infantiles de nivel superior, en lugar de detener el patio de recreo allí continuaremos girando el ciclo principal de ejecución, para que el código asíncrono tenga la oportunidad de ejecutarse. Eventualmente terminaremos el patio de juegos después de un tiempo de espera que por defecto es de 30 segundos, pero que se puede configurar si abre el editor asistente y muestra el asistente de la línea de tiempo; el tiempo de espera está en la esquina inferior derecha.
Por ejemplo, en Swift 3 (usando URLSession
lugar de NSURLConnection
):
import UIKit
import PlaygroundSupport
let url = URL(string: "http://.com")!
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
let contents = String(data: data, encoding: .utf8)
print(contents!)
}.resume()
PlaygroundPage.current.needsIndefiniteExecution = true
O en Swift 2:
import UIKit
import XCPlayground
let url = NSURL(string: "http://.com")
let request = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.currentQueue()) { response, maybeData, error in
if let data = maybeData {
let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
println(contents)
} else {
println(error.localizedDescription)
}
}
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
Swift 3, xcode 8, iOS 10
Notas:
Dile al compilador que el archivo del patio de recreo requiere "ejecución indefinida"
Termine la ejecución manualmente mediante una llamada a PlaygroundSupport.current.completeExecution()
dentro de su controlador de finalización.
Puede tener problemas con el directorio de caché y, para resolverlo, deberá reconstituir manualmente el singleton UICache.shared.
Ejemplo:
import UIKit
import Foundation
import PlaygroundSupport
// resolve path errors
URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
// identify that the current page requires "indefinite execution"
PlaygroundPage.current.needsIndefiniteExecution = true
// encapsulate execution completion
func completeExecution() {
PlaygroundPage.current.finishExecution()
}
let url = URL(string: "http://i.imgur.com/aWkpX3W.png")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
var image = UIImage(data: data!)
// complete execution
completeExecution()
}
task.resume()
Swift 4, Xcode 9.0
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else {
print(error?.localizedDescription ?? "")
return
}
if let data = data, let contents = String(data: data, encoding: String.Encoding.utf8) {
print(contents)
}
}
task.resume()
NSURLConnection.sendAsynchronousRequest(...)
NSRunLoop.currentRunLoop().run()