Pregunta

Muchos métodos Cocoa y CocoaTouch tienen devoluciones de llamada de finalización implementadas como bloques en Objective-C y cierres en Swift.Sin embargo, al probarlos en Playground, nunca se solicita 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 el println en mi bloque de finalización nunca se llaman...

¿Fue útil?

Solución

Si bien puede ejecutar un bucle de ejecución manualmente (o, para el código asíncrono que no requiere un bucle de ejecución, use otros métodos de espera como los semáforos de despacho), la forma en que proporcionamos en los parques infantiles para esperar a que sea asíncrono.El trabajo es importar el marco de XCPlayground y establecer GeneracDicetAcode.Si se ha establecido esta propiedad, cuando finaliza su fuente de juegos de nivel superior, en lugar de detener el patio de recreo, continuaremos girando para girar el bucle de ejecución principal, por lo que el código asíncrono tiene la oportunidad de ejecutarse.Eventualmente, terminaremos el patio de recreo después de un tiempo de espera, lo que se puede configurar a 30 segundos, pero que se puede configurar si abre el editor de asistentes y muestra el asistente de línea de tiempo;El tiempo de espera está en la parte inferior derecha.

Por ejemplo, en SWIFT 3 (usando XCPlaygroundPage.currentPage.needsIndefiniteExecution = true en lugar de URLSession):

import UIKit
import PlaygroundSupport

let url = URL(string: "http://stackoverflow.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://stackoverflow.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

Otros consejos

Esta API cambió nuevamente en Xcode 8 y se movió a la PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

Este cambio fue mencionado en Sesión 213 en la WWDC 2016.

A partir de XCode 7.1, XCPSetExecutionShouldContinueIndefinitely() es obsoleto.La forma correcta de hacer esto ahora es solicitar primero la ejecución indefinida como 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://stackoverflow.com")!) {
    result in
    print("Got result: \(result)")
    XCPlaygroundPage.currentPage.finishExecution()
}.resume()

La razón por la que no se llaman las devoluciones de llamada es porque la Runloop no se está ejecutando en el patio de recreo (o en el modo de respuesta para la materia).

Una forma algo jornada, pero efectiva, de hacer que las devoluciones de llamada funcionen con una bandera y luego iterando manualmente en el Runloop:

// Playground - noun: a place where people can play

import Cocoa
import XCPlayground

let url = NSURL(string: "http://stackoverflow.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 se ha utilizado a menudo en las pruebas de unidad que necesitan probar las devoluciones de llamada de ASYNC, por ejemplo: Patrón de cola principal de la cola principal" para la cola de prueba de la unidad que llama a la cola principal al finalizar

Las nuevas API que para Xcode 8, SWIFT 3 y iOS 10 son,

// import the module
import PlaygroundSupport
// write this at the beginning
PlaygroundPage.current.needsIndefiniteExecution = true
// To finish execution
PlaygroundPage.current.finishExecution()

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

SWIFT 3, Xcode 8, iOS 10

Notas:

Dígale al compilador que el archivo del patio de juegos requiere "ejecución indefinida"

Termine manualmente la ejecución a través de una llamada a PlaygroundSupport.current.completeExecution() dentro de su controlador de finalización.

Puede convertirte en problemas con el directorio de caché y para resolver esto, deberá volver a escribir manualmente la UICACHE.SHARED Singleton.

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

NSURLConnection.sendAsynchronousRequest(...)    
NSRunLoop.currentRunLoop().run()
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top