Come si eseguono callback asincroni in Playground


117

Molti metodi Cocoa e CocoaTouch hanno callback di completamento implementati come blocchi in Objective-C e Closures in Swift. Tuttavia, quando si provano questi in Playground, il completamento non viene mai chiamato. Per esempio:

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

Posso vedere l'output della console nella timeline di Playground, ma il printlnblocco di completamento non viene mai chiamato ...

Risposte:


186

Sebbene sia possibile eseguire manualmente un ciclo di esecuzione (o, per il codice asincrono che non richiede un ciclo di esecuzione, utilizzare altri metodi di attesa come i semafori di invio), il modo "integrato" che forniamo nei playground per attendere il lavoro asincrono è importa il XCPlaygroundframework e imposta XCPlaygroundPage.currentPage.needsIndefiniteExecution = true. Se questa proprietà è stata impostata, al termine della sorgente del playground di livello superiore, invece di fermare il playground continueremo a far girare il ciclo di esecuzione principale, in modo che il codice asincrono abbia la possibilità di essere eseguito. Alla fine termineremo il playground dopo un timeout predefinito di 30 secondi, ma che può essere configurato se apri l'assistente editor e visualizzi l'assistente della timeline; il timeout è in basso a destra.

Ad esempio, in Swift 3 (utilizzando al URLSessionposto di NSURLConnection):

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 in 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

1
Per quello che vale, questo è trattato in WWDC 2014 §408: Swift Playgrounds, seconda metà
Chris Conover

3
Da notare che da DP4 il XCPlaygroundframework è ora disponibile anche per iOS Playgrounds.
ikuramedia

4
Metodo aggiornato:XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
R Menke

23
Metodo aggiornato: import PlaygroundSupportePlaygroundPage.current.needsIndefiniteExecution = true
SimplGy

48

Questa API è cambiata di nuovo in Xcode 8 ed è stata spostata in PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

Questa modifica è stata menzionata nella sessione 213 del WWDC 2016 .


2
Non dimenticare di chiamare PlaygroundPage.current.finishExecution().
Glenn

36

A partire da XCode 7.1, XCPSetExecutionShouldContinueIndefinitely()è deprecato. Il modo corretto per farlo ora è richiedere prima l'esecuzione indefinita come proprietà della pagina corrente:

import XCPlayground

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

... quindi indicare quando l'esecuzione è terminata con:

XCPlaygroundPage.currentPage.finishExecution()

Per esempio:

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

16

Il motivo per cui i callback non vengono chiamati è perché RunLoop non è in esecuzione in Playground (o in modalità REPL per quella materia).

Un modo un po 'janky, ma efficace, per far funzionare i callback è con un flag e quindi iterando manualmente sul 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)
}

Questo modello è stato spesso utilizzato nei test unitari che richiedono di testare callback asincroni, ad esempio: Modello per test unitari coda asincrona che chiama la coda principale al completamento


8

Le nuove API per XCode8, Swift3 e iOS 10 sono,

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

5

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

3

Swift 3, xcode 8, iOS 10

Appunti:

Indica al compilatore che il file playground richiede "un'esecuzione indefinita"

Termina manualmente l'esecuzione tramite una chiamata a PlaygroundSupport.current.completeExecution() all'interno del gestore di completamento.

Potresti incorrere in problemi con la directory della cache e per risolverli dovrai reistanziare manualmente il singleton UICache.shared.

Esempio:

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

-3
NSURLConnection.sendAsynchronousRequest(...)    
NSRunLoop.currentRunLoop().run()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.