Wie führe ich asynchrone Rückrufe in Playground aus?
-
26-12-2019 - |
Frage
Bei vielen Cocoa- und CocoaTouch-Methoden sind Abschlussrückrufe als Blöcke in Objective-C und Closures in Swift implementiert.Wenn Sie diese jedoch in Playground ausprobieren, wird die Vervollständigung nie aufgerufen.Zum Beispiel:
// 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 println
in meinem Abschlussblock werden nie aufgerufen...
Lösung
Während Sie eine Ausführungsschleife manuell ausführen können (oder für asynchronen Code, der keine Ausführungsschleife erfordert, andere Wartemethoden wie Dispatch-Semaphoren verwenden können), besteht die „integrierte“ Möglichkeit, die wir in Playgrounds bereitstellen, um auf asynchrone Arbeit zu warten, darin Importieren Sie die XCPlayground
Rahmen und Set XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
.Wenn diese Eigenschaft festgelegt wurde, drehen wir die Hauptlaufschleife weiter, wenn Ihre Playground-Quelle der obersten Ebene beendet ist, anstatt den Playground dort anzuhalten, sodass asynchroner Code eine Chance hat, ausgeführt zu werden.Wir werden den Spielplatz schließlich nach einem Timeout beenden, das standardmäßig 30 Sekunden beträgt, aber 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 URLSession
anstatt 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
Andere Tipps
Diese API wurde in Xcode 8 erneut geändert und in die verschoben PlaygroundSupport
:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
Diese Änderung wurde in erwähnt Sitzung 213 auf der WWDC 2016.
Ab XCode 7.1, XCPSetExecutionShouldContinueIndefinitely()
ist veraltet.Der richtige Weg, dies jetzt zu tun, besteht darin, zunächst die unbefristete Ausführung als Eigenschaft der aktuellen Seite anzufordern:
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
…dann angeben, wann die Ausführung beendet ist mit:
XCPlaygroundPage.currentPage.finishExecution()
Zum Beispiel:
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()
Der Grund, warum die Rückrufe nicht aufgerufen werden, liegt darin, dass RunLoop nicht im Playground (oder im REPL-Modus) ausgeführt wird.
Eine etwas umständliche, aber effektive Möglichkeit, die Rückrufe zum Laufen zu bringen, besteht darin, ein Flag zu verwenden und dann manuell auf der Runloop 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 oft in Unit-Tests verwendet, die asynchrone Rückrufe testen müssen, zum Beispiel: Muster für Unit-Tests einer asynchronen Warteschlange, die nach Abschluss die Hauptwarteschlange aufruft
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()
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
Anmerkungen:
Teilen Sie dem Compiler mit, dass die Playground-Datei eine „unbestimmte Ausführung“ erfordert.
Beenden Sie die Ausführung manuell über einen Aufruf von PlaygroundSupport.current.completeExecution()
innerhalb Ihres Abschlusshandlers.
Möglicherweise treten Probleme mit dem Cache-Verzeichnis auf. Um dieses Problem zu beheben, müssen Sie den UICache.shared-Singleton 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()
NSURLConnection.sendAsynchronousRequest(...)
NSRunLoop.currentRunLoop().run()