如何在操场上运行异步回调?

时间:2023-01-23 17:15:09

Many Cocoa and CocoaTouch methods have completion callbacks implemented as blocks in Objective-C and Closures in Swift. However, when trying these out in Playground, the completion is never called. For example:

许多Cocoa和CocoaTouch方法都有在Objective-C中作为块实现的完成回调,在Swift中则是闭包。然而,当在操场上尝试这些,完成永远不会被调用。例如:

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

I can see the console output in my Playground timeline, but the println in my completion block are never called...

我可以在我的游乐场时间表中看到控制台输出,但是我的完成块中的println永远不会被调用……

8 个解决方案

#1


170  

While you can run a run loop manually (or, for asynchronous code that doesn't require a run loop, use other waiting methods like dispatch semaphores), the "built-in" way we provide in playgrounds to wait for asynchronous work is to import the XCPlayground framework and set XCPlaygroundPage.currentPage.needsIndefiniteExecution = true. If this property has been set, when your top level playground source finishes, instead of stopping the playground there we will continue to spin the main run loop, so asynchronous code has a chance to run. We will eventually terminate the playground after a timeout which defaults to 30 seconds, but which can be configured if you open the assistant editor and show the timeline assistant; the timeout is in the lower-right.

虽然您可以手动运行一个运行循环(或者,对于不需要运行循环的异步代码,可以使用其他等待方法,如分派信号量),但是我们在操场上提供的等待异步工作的“内置”方法是导入xc游乐场框架并设置xcplaygrounde.currentpage。needsIndefiniteExecution = true。如果设置了此属性,当您的*游乐场源代码完成时,我们将继续旋转主运行循环,而不是停止那里的游乐场,因此异步代码有机会运行。我们最终会在一个默认为30秒的超时后终止操场,但如果您打开助理编辑器并显示时间轴助理,则可以对其进行配置;超时在右下角。

For example, in Swift 3 (using URLSession instead of NSURLConnection):

例如,在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

Or in Swift 2:

或者在迅速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

#2


40  

This API changed again in Xcode 8 and it was moved to the PlaygroundSupport:

这个API在Xcode 8中再次更改,并被移动到PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

This change was mentioned in Session 213 at WWDC 2016.

这一变化在2016年世界金融大会第213届会议上被提及。

#3


33  

As of XCode 7.1, XCPSetExecutionShouldContinueIndefinitely() is deprecated. The correct way to do this now is to first request indefinite execution as a property of the current page:

在XCode 7.1中,不赞成使用xcpsetexecutionshouldcontinueinfinite()。正确的方法是首先请求不确定的执行作为当前页面的属性:

import XCPlayground

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

…then indicate when execution has finished with:

…然后指出何时执行完毕:

XCPlaygroundPage.currentPage.finishExecution()

For example:

例如:

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

#4


13  

The reason the callbacks are not called is because the RunLoop isn't running in Playground (or in REPL mode for that matter).

之所以不调用回调是因为RunLoop没有在游乐场中运行(或者在REPL模式中运行)。

A somewhat janky, but effective, way to make the callbacks operate is with a flag and then manually iterating on the runloop:

让回调操作的一种简单但有效的方法是使用一个标志,然后在runloop上手动迭代:

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

This pattern has often been used in Unit Tests which need to test async callbacks, for example: Pattern for unit testing async queue that calls main queue on completion

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

#5


7  

The new APIs as for XCode8, Swift3 and iOS 10 are,

XCode8、Swift3和ios10的新api有:

// import the module
import PlaygroundSupport
// write this at the beginning
PlaygroundPage.current.needsIndefiniteExecution = true
// To finish execution
PlaygroundPage.current.finishExecution()

#6


4  

Swift 4, Xcode 9.0

斯威夫特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()

#7


3  

Swift 3, xcode 8, iOS 10

Swift 3, xcode 8, ios10

Notes:

注:

Tell the compiler that the playground file requires "indefinite execution"

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

Manually terminate execution via a call to PlaygroundSupport.current.completeExecution() within your completion handler.

通过调用PlaygroundSupport.current.completeExecution()在完成处理程序中手动终止执行。

You may run into problems with the cache directory and to resolve this you will need to manually re-instantiate the UICache.shared singleton.

您可能会遇到缓存目录的问题,要解决这个问题,您需要手动重新实例化UICache。共享的单例。

Example:

例子:

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


-3  

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

#1


170  

While you can run a run loop manually (or, for asynchronous code that doesn't require a run loop, use other waiting methods like dispatch semaphores), the "built-in" way we provide in playgrounds to wait for asynchronous work is to import the XCPlayground framework and set XCPlaygroundPage.currentPage.needsIndefiniteExecution = true. If this property has been set, when your top level playground source finishes, instead of stopping the playground there we will continue to spin the main run loop, so asynchronous code has a chance to run. We will eventually terminate the playground after a timeout which defaults to 30 seconds, but which can be configured if you open the assistant editor and show the timeline assistant; the timeout is in the lower-right.

虽然您可以手动运行一个运行循环(或者,对于不需要运行循环的异步代码,可以使用其他等待方法,如分派信号量),但是我们在操场上提供的等待异步工作的“内置”方法是导入xc游乐场框架并设置xcplaygrounde.currentpage。needsIndefiniteExecution = true。如果设置了此属性,当您的*游乐场源代码完成时,我们将继续旋转主运行循环,而不是停止那里的游乐场,因此异步代码有机会运行。我们最终会在一个默认为30秒的超时后终止操场,但如果您打开助理编辑器并显示时间轴助理,则可以对其进行配置;超时在右下角。

For example, in Swift 3 (using URLSession instead of NSURLConnection):

例如,在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

Or in Swift 2:

或者在迅速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

#2


40  

This API changed again in Xcode 8 and it was moved to the PlaygroundSupport:

这个API在Xcode 8中再次更改,并被移动到PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

This change was mentioned in Session 213 at WWDC 2016.

这一变化在2016年世界金融大会第213届会议上被提及。

#3


33  

As of XCode 7.1, XCPSetExecutionShouldContinueIndefinitely() is deprecated. The correct way to do this now is to first request indefinite execution as a property of the current page:

在XCode 7.1中,不赞成使用xcpsetexecutionshouldcontinueinfinite()。正确的方法是首先请求不确定的执行作为当前页面的属性:

import XCPlayground

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

…then indicate when execution has finished with:

…然后指出何时执行完毕:

XCPlaygroundPage.currentPage.finishExecution()

For example:

例如:

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

#4


13  

The reason the callbacks are not called is because the RunLoop isn't running in Playground (or in REPL mode for that matter).

之所以不调用回调是因为RunLoop没有在游乐场中运行(或者在REPL模式中运行)。

A somewhat janky, but effective, way to make the callbacks operate is with a flag and then manually iterating on the runloop:

让回调操作的一种简单但有效的方法是使用一个标志,然后在runloop上手动迭代:

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

This pattern has often been used in Unit Tests which need to test async callbacks, for example: Pattern for unit testing async queue that calls main queue on completion

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

#5


7  

The new APIs as for XCode8, Swift3 and iOS 10 are,

XCode8、Swift3和ios10的新api有:

// import the module
import PlaygroundSupport
// write this at the beginning
PlaygroundPage.current.needsIndefiniteExecution = true
// To finish execution
PlaygroundPage.current.finishExecution()

#6


4  

Swift 4, Xcode 9.0

斯威夫特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()

#7


3  

Swift 3, xcode 8, iOS 10

Swift 3, xcode 8, ios10

Notes:

注:

Tell the compiler that the playground file requires "indefinite execution"

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

Manually terminate execution via a call to PlaygroundSupport.current.completeExecution() within your completion handler.

通过调用PlaygroundSupport.current.completeExecution()在完成处理程序中手动终止执行。

You may run into problems with the cache directory and to resolve this you will need to manually re-instantiate the UICache.shared singleton.

您可能会遇到缓存目录的问题,要解决这个问题,您需要手动重新实例化UICache。共享的单例。

Example:

例子:

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


-3  

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