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

Многие методы 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 в моем блоке завершения никогда не вызывается …

Хотя вы можете запускать цикл выполнения вручную (или для асинхронного кода, который не требует цикла запуска, использовать другие методы ожидания, такие как семафоры отправки), «встроенный» способ, который мы предоставляем на игровых площадках, чтобы ждать асинхронной работы, – это импортируйте XCPlayground и установите XCPlaygroundPage.currentPage.needsIndefiniteExecution = true . Если это свойство было установлено, когда ваш верхний уровень игровой площадки заканчивается, вместо остановки игровой площадки мы будем продолжать вращать основной цикл запуска, поэтому asynchronous код имеет шанс запустить. В конечном итоге мы закончим игровую площадку после тайм-аута, который по умолчанию составляет 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 

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

Начиная с 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, если на то пошло).

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

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

Новые 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() 

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

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://sofru.miximages.com/asynchronous/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() 
Interesting Posts

Не удается разрешить% windir% / Невозможно изменить% path% или% path%, сбрасываемую при загрузке

Как исправить ассоциацию файлов .exe в Windows?

100% -ное использование диска – ноутбук постоянно замерзает, когда он всплескивает

Удалите раздел OSX и добавьте остаток в раздел Windows 7

Циркулярные ссылки Причина утечки памяти?

Возвращает локальную строку как срез (& str)

Как остановить мой Mac от запроса принимать входящие сетевые подключения?

Android Studio 2.0 – пауза / белый экран при первом запуске приложения

Установите выделенный индекс в раскрывающемся списке, используя jQuery

Пакетное преобразование документов Word в PDF-файлы

Использование 32-разрядного приложения на 64-битной

Поддерживается ли GTX 960 в Intel Chipset Q75?

Преобразование из GLSurfaceView в TextureView (через GLTextureView)

Android: Как получить текущее смещение X для RecyclerView?

Как изменить поля внутри нового типа данных PostgreSQL JSON?

Давайте будем гением компьютера.