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