Как запустить асинхронные обратные вызовы на игровой площадке

Многие методы Cocoa и CocoaTouch имеют завершающие обратные вызовы, реализованные как блоки в Objective-C и Closures in 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 в моем блоке завершения никогда не вызывается...

Ответ 1

Пока вы можете запускать цикл выполнения вручную (или для асинхронного кода, который не требует цикла запуска, использовать другие методы ожидания, такие как семафоры отправки), "встроенный" способ, который мы предоставляем на игровых площадках, чтобы ждать асинхронных работа заключается в том, чтобы импортировать структуру 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

Ответ 2

Этот API снова изменился в Xcode 8 и был перенесен в PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

Это изменение было упомянуто в сеансе 213 на WWDC 2016.

Ответ 3

Начиная с 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()

Ответ 4

Причина, по которой вызовы не вызываются, заключается в том, что RunLoop не работает в Playground (или в режиме REPL, если на то пошло).

Несколько janky, но эффективный способ заставить обратные вызовы работать с флагом, а затем вручную итерации на 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)
}

Этот шаблон часто использовался в модульных тестах, которые должны тестировать асинхронные обратные вызовы, например: Образец для асинхронной очереди модульного тестирования, который вызывает основную очередь при завершении

Ответ 5

Новые API, как для XCode8, Swift3 и iOS 10,

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

Ответ 6

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

Ответ 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

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