Question

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.Cependant, lorsque vous les essayez dans Playground, la complétion n'est jamais appelée.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 ma timeline Playground, mais le println dans mon bloc d'achèvement ne sont jamais appelés...

Était-ce utile?

La solution

Bien que vous puissiez exécuter une boucle d'exécution manuellement (ou, pour le code asynchrone qui ne nécessite pas de boucle d'exécution, utiliser d'autres méthodes d'attente comme les sémaphores de répartition), la manière "intégrée" que nous proposons dans les terrains de jeux pour attendre le travail asynchrone est de importer le XCPlayground cadre et ensemble XCPlaygroundPage.currentPage.needsIndefiniteExecution = true.Si cette propriété a été définie, une fois la source de votre terrain de jeu de niveau supérieur terminée, au lieu d'arrêter le terrain de jeu à cet endroit, nous continuerons à faire tourner la boucle d'exécution principale, afin que le code asynchrone ait une chance de s'exécuter.Nous finirons par terminer le terrain de jeu après un délai d'attente qui est par défaut de 30 secondes, mais qui peut être configuré si vous ouvrez l'éditeur d'assistant et affichez l'assistant de chronologie ;le délai d'attente 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

Autres conseils

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

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

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

Depuis XCode 7.1, XCPSetExecutionShouldContinueIndefinitely() est obsolète.La bonne façon de procéder maintenant est de demander d’abord une exécution indéfinie en tant que propriété de la page actuelle :

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

La raison pour laquelle les rappels ne sont pas appelés est que RunLoop ne s'exécute pas en Playground (ni en mode REPL d'ailleurs).

Une façon un peu bizarre, mais efficace, de faire fonctionner les rappels consiste à utiliser un indicateur, puis à itérer manuellement sur la boucle d'exécution :

// 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 qui doivent tester les rappels asynchrones, par exemple : Modèle de file d'attente asynchrone de tests unitaires qui appelle la file d'attente principale à la fin

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

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

Remarques:

Dites au compilateur que le fichier du 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 et 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()
NSURLConnection.sendAsynchronousRequest(...)    
NSRunLoop.currentRunLoop().run()
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top