How do I run Asynchronous callbacks in Playground

许多 Cocoa 和 CocoaTouch 方法在 Objective-C 中以块的形式实现完成回调,在 Swift 中实现闭包。然而,当在游乐场尝试这些,完成从来没有被称为。例如:

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

我可以看到控制台输出在我的 Playground 时间线,但 println在我的完成块从来没有调用..。

38312 次浏览

不调用回调的原因是 RunLoop 没有在 Playground (或 REPL 模式)中运行。

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

这种模式经常用于需要测试异步回调的单元测试中,例如: Pattern for unit testing async queue that calls main queue on completion

虽然您可以手动运行一个运行循环(或者,对于不需要运行循环的异步代码,使用其他等待方法,如分派信号量) ,我们在游乐场中提供的等待异步工作的“内置”方式是导入 XCPlayground框架并设置 XCPlaygroundPage.currentPage.needsIndefiniteExecution = true。如果这个属性已经设置,当您的顶级游乐场源完成,而不是停止游乐场那里,我们将继续旋转主运行循环,所以异步代码有机会运行。我们将最终终止操场后的超时默认为30秒,但可以配置,如果你打开助理编辑器和显示时间线助理; 超时是在右下角。

例如,在 Swift 3中(使用 URLSession而不是 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

或者在 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
NSURLConnection.sendAsynchronousRequest(...)
NSRunLoop.currentRunLoop().run()

从 XCode 7.1开始,不推荐使用 XCPSetExecutionShouldContinueIndefinitely()。现在正确的方法是首先请求作为当前页面属性的不确定执行:

import XCPlayground


XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

... 然后指示何时执行结束:

XCPlaygroundPage.currentPage.finishExecution()

例如:

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

这个 API 在 Xcode 8中又发生了变化,它被移到了 PlaygroundSupport:

import PlaygroundSupport


PlaygroundPage.current.needsIndefiniteExecution = true

这个更改在 2016年 WWDC 第213届会议中提到过。

XCode8、 Swift3和 iOS10的新 API 是,

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

Swift 3 xcode 8 iOS 10

备注:

告诉编译器操场文件需要“无限期执行”

Manually terminate execution via a call to PlaygroundSupport.current.completeExecution() within your completion handler.

您可能会遇到缓存目录的问题,为了解决这个问题,您需要手动重新实例化 UICache.share 单例。

例如:

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

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