Wie führe ich asynchrone Rückrufe in Playground aus?

117

Bei vielen Cocoa- und CocoaTouch-Methoden sind Abschlussrückrufe als Blöcke in Objective-C und Closures in Swift implementiert. Wenn Sie diese jedoch auf dem Spielplatz ausprobieren, wird die Fertigstellung niemals aufgerufen. Beispielsweise:

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

Ich kann die Konsolenausgabe in meiner Playground-Timeline sehen, aber die printlnin meinem Abschlussblock werden nie aufgerufen ...

ikuramedia
quelle

Antworten:

186

Während Sie eine Ausführungsschleife manuell ausführen können (oder für asynchronen Code, für den keine Ausführungsschleife erforderlich ist, andere Wartemethoden wie Versandsemaphoren verwenden können), bieten wir auf Spielplätzen die "integrierte" Möglichkeit, auf asynchrone Arbeit zu warten importiere das XCPlaygroundFramework und setze XCPlaygroundPage.currentPage.needsIndefiniteExecution = true. Wenn diese Eigenschaft festgelegt wurde und die Spielplatzquelle der obersten Ebene fertig ist, drehen wir die Hauptlaufschleife weiter, anstatt den Spielplatz dort anzuhalten, sodass asynchroner Code ausgeführt werden kann. Wir werden den Spielplatz schließlich nach einer Zeitüberschreitung beenden, die standardmäßig 30 Sekunden beträgt, die jedoch konfiguriert werden kann, wenn Sie den Assistenten-Editor öffnen und den Timeline-Assistenten anzeigen. Das Timeout befindet sich unten rechts.

Zum Beispiel in Swift 3 (mit URLSessionanstelle von 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

Oder 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
Rick Ballard
quelle
1
Für alles, was es wert ist, wird dies in WWDC 2014 §408: Schnelle Spielplätze, zweite Hälfte
Chris Conover
3
Erwähnenswert ist, dass das XCPlaygroundFramework ab DP4 jetzt auch für iOS-Spielplätze verfügbar ist.
Ikuramedia
4
Aktualisierte Methode:XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
R Menke
23
Aktualisierte Methode: import PlaygroundSupportundPlaygroundPage.current.needsIndefiniteExecution = true
SimplGy
48

Diese API wurde in Xcode 8 erneut geändert und in Folgendes verschoben PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

Diese Änderung wurde in Sitzung 213 auf der WWDC 2016 erwähnt .

BalestraPatrick
quelle
2
Vergiss nicht anzurufen PlaygroundPage.current.finishExecution().
Glenn
36

Ab XCode 7.1 XCPSetExecutionShouldContinueIndefinitely()ist veraltet. Der richtige Weg, dies jetzt zu tun, besteht darin, zuerst eine unbestimmte Ausführung als Eigenschaft der aktuellen Seite anzufordern:

import XCPlayground

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

… Geben Sie dann an, wann die Ausführung abgeschlossen ist mit:

XCPlaygroundPage.currentPage.finishExecution()

Beispielsweise:

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()
Paul Cantrell
quelle
16

Der Grund, warum die Rückrufe nicht aufgerufen werden, ist, dass RunLoop nicht auf dem Spielplatz (oder im REPL-Modus) ausgeführt wird.

Eine etwas ruckelige, aber effektive Möglichkeit, die Rückrufe zum Laufen zu bringen, besteht darin, ein Flag zu verwenden und dann den Runloop manuell zu iterieren:

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

Dieses Muster wurde häufig in Komponententests verwendet, bei denen asynchrone Rückrufe getestet werden müssen, z. B.: Muster zum Testen von asynchronen Warteschlangen, die nach Abschluss die Hauptwarteschlange aufrufen

ikuramedia
quelle
8

Die neuen APIs für XCode8, Swift3 und iOS 10 sind:

// import the module
import PlaygroundSupport
// write this at the beginning
PlaygroundPage.current.needsIndefiniteExecution = true
// To finish execution
PlaygroundPage.current.finishExecution()
bradd123
quelle
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()
p-Sonne
quelle
3

Swift 3, Xcode 8, iOS 10

Anmerkungen:

Teilen Sie dem Compiler mit, dass für die Spielplatzdatei eine "unbestimmte Ausführung" erforderlich ist.

Beenden Sie die Ausführung manuell über einen Aufruf an PlaygroundSupport.current.completeExecution()Ihren Completion-Handler.

Möglicherweise treten Probleme mit dem Cache-Verzeichnis auf. Um dieses Problem zu beheben, müssen Sie den Singleton UICache.shared manuell neu instanziieren.

Beispiel:

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()
Lloyd Briggs
quelle
-3
NSURLConnection.sendAsynchronousRequest(...)    
NSRunLoop.currentRunLoop().run()
Tony Pan
quelle