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

117

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

ikuramedia
источник

Ответы:

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, вторая половина
Крис Коновер
3
Стоит отметить, что из DP4 XCPlaygroundфреймворк теперь доступен и для игровых площадок iOS.
ikuramedia
4
Обновленный метод:XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
R Menke
23
Обновленный метод: import PlaygroundSupportandPlaygroundPage.current.needsIndefiniteExecution = true
SimplGy
48

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

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

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

BalestraPatrick
источник
2
Не забудь позвонить PlaygroundPage.current.finishExecution().
Гленн
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, если на то пошло).

Несколько дрянный, но эффективный способ заставить обратные вызовы работать - это установить флаг, а затем вручную выполнить итерацию цикла выполнения:

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

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

ikuramedia
источник
8

Новые 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()
bradd123
источник
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()
Тони Пэн
источник