如何在 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 PlaygroundSupportPlaygroundPage.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 中运行异步回调的主要内容,如果未能解决你的问题,请参考以下文章

如何运行多个异步函数然后执行回调?

如何通过 libwebsocket 发送异步数据?

Java--回调接口

js的执行和调试

Java之异步回调

GWT 单线程异步回调