How do I run Asynchronous callbacks in Playground
Solution 1
While you can run a run loop manually (or, for asynchronous code that doesn't require a run loop, use other waiting methods like dispatch semaphores), the "built-in" way we provide in playgrounds to wait for asynchronous work is to import the XCPlayground
framework and set XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
. If this property has been set, when your top level playground source finishes, instead of stopping the playground there we will continue to spin the main run loop, so asynchronous code has a chance to run. We will eventually terminate the playground after a timeout which defaults to 30 seconds, but which can be configured if you open the assistant editor and show the timeline assistant; the timeout is in the lower-right.
For example, in Swift 3 (using URLSession
instead of 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
Or 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
Solution 2
This API changed again in Xcode 8 and it was moved to the PlaygroundSupport
:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
This change was mentioned in Session 213 at WWDC 2016.
Solution 3
As of XCode 7.1, XCPSetExecutionShouldContinueIndefinitely()
is deprecated. The correct way to do this now is to first request indefinite execution as a property of the current page:
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
…then indicate when execution has finished with:
XCPlaygroundPage.currentPage.finishExecution()
For example:
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()
Solution 4
The reason the callbacks are not called is because the RunLoop isn't running in Playground (or in REPL mode for that matter).
A somewhat janky, but effective, way to make the callbacks operate is with a flag and then manually iterating on the 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)
}
This pattern has often been used in Unit Tests which need to test async callbacks, for example: Pattern for unit testing async queue that calls main queue on completion
Solution 5
The new APIs as for XCode8, Swift3 and iOS 10 are,
// import the module
import PlaygroundSupport
// write this at the beginning
PlaygroundPage.current.needsIndefiniteExecution = true
// To finish execution
PlaygroundPage.current.finishExecution()
ikuramedia
App Development, Design, Investment & Consulting Empower your business, engage your customers and use data to make better decisions. ikuramedia.com Mobile A/B Optimisation that works, easily splitforce.com
Updated on July 08, 2022Comments
-
ikuramedia almost 2 years
Many Cocoa and CocoaTouch methods have completion callbacks implemented as blocks in Objective-C and Closures in Swift. However, when trying these out in Playground, the completion is never called. For example:
// 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) } }
I can see the console output in my Playground timeline, but the
println
in my completion block are never called... -
Chris Conover almost 10 yearsFor all it's worth, This is covered in WWDC 2014 §408: Swift Playgrounds, second half
-
ikuramedia almost 10 yearsWorth noting that from DP4 the
XCPlayground
framework is now available for iOS Playgrounds as well. -
R Menke over 8 yearsUpdated method :
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
-
SimplGy almost 8 yearsUpdated method:
import PlaygroundSupport
andPlaygroundPage.current.needsIndefiniteExecution = true
-
Glenn Posadas over 6 yearsDon't forget to call
PlaygroundPage.current.finishExecution()
.