Frage

Bei vielen Cocoa- und CocoaTouch-Methoden sind Abschlussrückrufe als Blöcke in Objective-C und Closures in Swift implementiert.Wenn Sie diese jedoch in Playground ausprobieren, wird die Vervollständigung nie aufgerufen.Zum Beispiel:

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

Ich kann die Konsolenausgabe in meiner Playground-Timeline sehen, aber die println in meinem Abschlussblock werden nie aufgerufen...

War es hilfreich?

Lösung

Während Sie eine Ausführungsschleife manuell ausführen können (oder für asynchronen Code, der keine Ausführungsschleife erfordert, andere Wartemethoden wie Dispatch-Semaphoren verwenden können), besteht die „integrierte“ Möglichkeit, die wir in Playgrounds bereitstellen, um auf asynchrone Arbeit zu warten, darin Importieren Sie die XCPlayground Rahmen und Set XCPlaygroundPage.currentPage.needsIndefiniteExecution = true.Wenn diese Eigenschaft festgelegt wurde, drehen wir die Hauptlaufschleife weiter, wenn Ihre Playground-Quelle der obersten Ebene beendet ist, anstatt den Playground dort anzuhalten, sodass asynchroner Code eine Chance hat, ausgeführt zu werden.Wir werden den Spielplatz schließlich nach einem Timeout beenden, das standardmäßig 30 Sekunden beträgt, aber konfiguriert werden kann, wenn Sie den Assistenten-Editor öffnen und den Timeline-Assistenten anzeigen;Das Timeout befindet sich unten rechts.

Zum Beispiel in Swift 3 (mit URLSession anstatt 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

Oder in 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

Andere Tipps

Diese API wurde in Xcode 8 erneut geändert und in die verschoben PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

Diese Änderung wurde in erwähnt Sitzung 213 auf der WWDC 2016.

Ab XCode 7.1, XCPSetExecutionShouldContinueIndefinitely() ist veraltet.Der richtige Weg, dies jetzt zu tun, besteht darin, zunächst die unbefristete Ausführung als Eigenschaft der aktuellen Seite anzufordern:

import XCPlayground

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

…dann angeben, wann die Ausführung beendet ist mit:

XCPlaygroundPage.currentPage.finishExecution()

Zum Beispiel:

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

Der Grund, warum die Rückrufe nicht aufgerufen werden, liegt darin, dass RunLoop nicht im Playground (oder im REPL-Modus) ausgeführt wird.

Eine etwas umständliche, aber effektive Möglichkeit, die Rückrufe zum Laufen zu bringen, besteht darin, ein Flag zu verwenden und dann manuell auf der Runloop zu iterieren:

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

Dieses Muster wurde oft in Unit-Tests verwendet, die asynchrone Rückrufe testen müssen, zum Beispiel: Muster für Unit-Tests einer asynchronen Warteschlange, die nach Abschluss die Hauptwarteschlange aufruft

Die neuen APIs für XCode8, Swift3 und iOS 10 sind:

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

Anmerkungen:

Teilen Sie dem Compiler mit, dass die Playground-Datei eine „unbestimmte Ausführung“ erfordert.

Beenden Sie die Ausführung manuell über einen Aufruf von PlaygroundSupport.current.completeExecution() innerhalb Ihres Abschlusshandlers.

Möglicherweise treten Probleme mit dem Cache-Verzeichnis auf. Um dieses Problem zu beheben, müssen Sie den UICache.shared-Singleton manuell neu instanziieren.

Beispiel:

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()
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top