如何在 Playground 中运行异步回调
Posted
技术标签:
【中文标题】如何在 Playground 中运行异步回调【英文标题】:How do I run Asynchronous callbacks in Playground 【发布时间】:2014-07-26 08:05:21 【问题描述】:许多 Cocoa 和 CocoaTouch 方法都有完成回调,在 Objective-C 中实现为块,在 Swift 中实现为闭包。但是,当在 Playground 中尝试这些时,永远不会调用完成。例如:
// Playground - noun: a place where people can play
import Cocoa
import XCPlayground
let url = NSURL(string: "http://***.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
从未被调用...
【问题讨论】:
【参考方案1】:未调用回调的原因是 RunLoop 未在 Playground 中运行(或在 REPL 模式下)。
使回调运行的一种有点笨拙但有效的方法是使用一个标志,然后在运行循环上手动迭代:
// Playground - noun: a place where people can play
import Cocoa
import XCPlayground
let url = NSURL(string: "http://***.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
【讨论】:
【参考方案2】:虽然您可以手动运行运行循环(或者,对于不需要运行循环的异步代码,使用其他等待方法,例如分派信号量),但我们在 Playground 中提供的“内置”方式来等待异步工作是导入XCPlayground
框架并设置XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
。如果此属性已设置,当您的*** Playground 源代码完成时,我们将继续旋转主运行循环,而不是在那里停止 Playground,因此异步代码有机会运行。我们最终会在默认为 30 秒的超时后终止 Playground,但如果您打开助手编辑器并显示时间线助手,则可以进行配置;超时在右下角。
例如,在 Swift 3 中(使用 URLSession
而不是 NSURLConnection
):
import UIKit
import PlaygroundSupport
let url = URL(string: "http://***.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://***.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
【讨论】:
无论如何,这在 WWDC 2014 §408: Swift Playgrounds, 下半场中有介绍 值得注意的是,从 DP4 开始,XCPlayground
框架现在也可用于 ios Playgrounds。
更新方法:XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
更新方法:import PlaygroundSupport
和 PlaygroundPage.current.needsIndefiniteExecution = true
【参考方案3】:
NSURLConnection.sendAsynchronousRequest(...)
NSRunLoop.currentRunLoop().run()
【讨论】:
【参考方案4】:从 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://***.com")!)
result in
print("Got result: \(result)")
XCPlaygroundPage.currentPage.finishExecution()
.resume()
【讨论】:
【参考方案5】:此 API 在 Xcode 8 中再次更改,并移至 PlaygroundSupport
:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
Session 213 at WWDC 2016 中提到了此更改。
【讨论】:
别忘了给PlaygroundPage.current.finishExecution()
打电话。【参考方案6】:
XCode8、Swift3 和 iOS 10 的新 API 是,
// import the module
import PlaygroundSupport
// write this at the beginning
PlaygroundPage.current.needsIndefiniteExecution = true
// To finish execution
PlaygroundPage.current.finishExecution()
【讨论】:
【参考方案7】:Swift 3、xcode 8、iOS 10
注意事项:
告诉编译器操场文件需要“无限期执行”
通过在完成处理程序中调用 PlaygroundSupport.current.completeExecution()
手动终止执行。
您可能会遇到缓存目录问题,要解决此问题,您需要手动重新实例化 UICache.shared 单例。
示例:
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()
【讨论】:
【参考方案8】: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()
【讨论】:
以上是关于如何在 Playground 中运行异步回调的主要内容,如果未能解决你的问题,请参考以下文章