许多 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 在我的完成块中从未被调用过......

有帮助吗?

解决方案

虽然您可以手动运行运行循环(或者,对于不需要运行循环的异步代码,可以使用调度信号量等其他等待方法),但我们在 Playground 中提供的等待异步工作的“内置”方式是导入 XCPlayground 框架和集合 XCPlaygroundPage.currentPage.needsIndefiniteExecution = true. 。如果已设置此属性,则当您的顶级 Playground 源完成时,我们将继续旋转主运行循环,而不是停止 Playground,因此异步代码有机会运行。我们最终将在超时后终止 Playground,超时默认为 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

其他提示

这个 API 在 Xcode 8 中再次发生变化,并被移至 PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

这一变化在 WWDC 2016 第 213 场会议.

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

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

使回调运行的一种有点笨拙但有效的方法是使用标志,然后在运行循环上手动迭代:

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

这种模式经常用于需要测试异步回调的单元测试,例如: 用于在完成时调用主队列的单元测试异步队列的模式

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

斯威夫特 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、Xcode 8、iOS 10

笔记:

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

通过调用手动终止执行 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()
NSURLConnection.sendAsynchronousRequest(...)    
NSRunLoop.currentRunLoop().run()
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top