如何在Playground中运行异步回调

时间:2014-06-05 10:55:43

标签: asynchronous callback closures swift swift-playground

许多Cocoa和CocoaTouch方法都有完成回调,实现为Objective-C中的块和Swift中的闭包。但是,在Playground中尝试这些时,永远不会调用完成。例如:

// 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永远不会被调用...

8 个答案:

答案 0 :(得分:180)

虽然你可以手动运行一个运行循环(或者,对于不需要运行循环的异步代码,使用其他等待方法,如调度信号量),我们在playground中提供的“内置”方式等待异步工作是导入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

答案 1 :(得分:47)

此API在Xcode 8中再次更改,并已移至PlaygroundSupport

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

Session 213 at WWDC 2016中提到了此更改。

答案 2 :(得分:36)

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

答案 3 :(得分:15)

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

使回调操作的一种有点笨拙但有效的方法是使用标志,然后在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

答案 4 :(得分:8)

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

答案 5 :(得分:5)

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

答案 6 :(得分:3)

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

答案 7 :(得分:-3)

NSURLConnection.sendAsynchronousRequest(...)    
NSRunLoop.currentRunLoop().run()