如何在Playground中运行异步回调


117

许多Cocoa和CocoaTouch方法都将完成回调实现为Objective-C中的块,而实现为Swift中的Closures。但是,在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在我的完成块中从未调用过...

Answers:


186

虽然您可以手动运行一个运行循环(或者,对于不需要运行循环的异步代码,请使用其他等待方法,例如调度信号量),但我们在操场上提供的“内置”方法是等待异步工作导入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
尽一切可能,这在WWDC 2014§408:Swift Playgrounds,下半年
Chris Conover

3
值得注意的是,从DP4开始,该XCPlayground框架现在也可用于iOS Playgrounds。
ikuramedia

4
更新方法:XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
R Menke

23
更新的方法:import PlaygroundSupportPlaygroundPage.current.needsIndefiniteExecution = true
SimplGy

48

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

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

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


2
别忘了打电话PlaygroundPage.current.finishExecution()
Glenn

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

16

未调用回调的原因是因为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)
}

此模式通常在需要测试异步回调的单元测试中使用,例如:单元测试异步队列的模式,该模式在完成时调用主队列


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

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

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

-3
NSURLConnection.sendAsynchronousRequest(...)    
NSRunLoop.currentRunLoop().run()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.