web-dev-qa-db-fra.com

Comment exécuter des rappels asynchrones dans Playground

De nombreuses méthodes Cocoa et CocoaTouch ont des rappels d'achèvement implémentés sous forme de blocs dans Objective-C et de Closures dans Swift. Toutefois, lors de l’essai dans Playground, l’achèvement n’est jamais appelé. Par exemple:

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

Je peux voir la sortie de la console dans mon scénario Playground, mais les println de mon bloc d'achèvement ne sont jamais appelés ...

114
ikuramedia

Bien que vous puissiez exécuter une boucle d'exécution manuellement (ou, pour du code asynchrone ne nécessitant pas de boucle d'exécution, utilisez d'autres méthodes en attente, telles que les sémaphores de dispatch), la méthode "intégrée" que nous fournissons aux terrains de jeu pour attendre le travail asynchrone consiste à: importer le framework XCPlayground et définir XCPlaygroundPage.currentPage.needsIndefiniteExecution = true. Si cette propriété a été définie, lorsque la source de votre terrain de jeu de niveau supérieur se termine, au lieu d'arrêter le terrain de jeu ici, nous continuons à faire tourner la boucle d'exécution principale afin qu'un code asynchrone ait une chance de s'exécuter. Nous finirons par terminer le terrain de jeu après un délai dépassant par défaut 30 secondes, mais pouvant être configuré si vous ouvrez l’éditeur d’assistant et affichez l’assistant de la timeline; la temporisation est en bas à droite.

Par exemple, dans Swift 3 (en utilisant URLSession au lieu de 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

Ou dans 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
182
Rick Ballard

Cette API a encore changé dans Xcode 8 et elle a été déplacée vers le PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

Ce changement a été mentionné dans Session 213 à WWDC 2016 .

47
BalestraPatrick

À partir de XCode 7.1, XCPSetExecutionShouldContinueIndefinitely() est obsolète. La bonne façon de faire ceci maintenant est de demander d'abord l'exécution indéfinie en tant que propriété de la page en cours:

import XCPlayground

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

… Puis indiquez quand l'exécution est terminée avec:

XCPlaygroundPage.currentPage.finishExecution()

Par exemple:

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()
36
Paul Cantrell

La raison pour laquelle les callbacks ne sont pas appelés est que RunLoop ne fonctionne pas dans Playground (ou en mode REPL d'ailleurs).

Un moyen assez janky, mais efficace, de faire fonctionner les callbacks est avec un drapeau, puis une itération manuelle sur le 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)
}

Ce modèle a souvent été utilisé dans les tests unitaires nécessitant de tester les rappels asynchrones, par exemple: Modèle pour la file d'attente asynchrone testant les unités qui appelle la file principale à la fin

15
ikuramedia

Les nouvelles API comme pour XCode8, Swift3 et iOS 10 sont,

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

Swift 4, Xcode 9.

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()
5
p-sun

Swift 3, xcode 8, iOS 10

Notes:

Dire au compilateur que le fichier de terrain de jeu nécessite une "exécution indéfinie"

Terminez manuellement l'exécution via un appel à PlaygroundSupport.current.completeExecution() dans votre gestionnaire d'achèvement.

Vous pouvez rencontrer des problèmes avec le répertoire de cache. Pour résoudre ce problème, vous devrez ré-instancier manuellement le singleton UICache.shared.

Exemple:

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
Lloyd Briggs