Як запустити асинхронні зворотні дзвінки на дитячому майданчику


117

Багато методів 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)
    }
}

Я можу бачити вихід консолі на моїй шкалі майданчика, але printlnв блоці мого завершення ніколи не викликається ...

Відповіді:


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: Швидкі ігрові майданчики, друга половина
Кріс Коновер

3
Варто відзначити, що з DP4 XCPlaygroundрамки тепер доступні і для iOS Playgrounds.
ikuramedia

4
Оновлений метод:XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
R Menke

23
Оновлений метод: import PlaygroundSupportтаPlaygroundPage.current.needsIndefiniteExecution = true
SimplGy

48

Цей API знову змінився в Xcode 8 і його було переміщено до PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

Про цю зміну згадувалося на сесії 213 на WWDC 2016 .


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 не працює в ігровому майданчику (або в режимі 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)
}

Ця закономірність часто застосовується в модульних тестах, які потребують тестування зворотних викликів асинхронізації, наприклад: Шаблон для тестування одиниці черги асинхронізації, яка викликає основну чергу після завершення


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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.