Jak uruchomić asynchroniczne wywołania zwrotne w Playground


117

Wiele metod Cocoa i CocoaTouch ma wywołania zwrotne zakończenia zaimplementowane jako bloki w Objective-C i Closures w Swift. Jednak podczas wypróbowywania ich w Playground zakończenie nigdy nie jest wywoływane. Na przykład:

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

Widzę wyjście konsoli na mojej osi czasu Playground, ale printlnw moim bloku ukończenia nigdy nie są wywoływane ...

Odpowiedzi:


186

Chociaż pętlę uruchamiania można uruchomić ręcznie (lub w przypadku kodu asynchronicznego, który nie wymaga pętli uruchamiania, można użyć innych metod oczekiwania, takich jak semafory wysyłające), „wbudowanym” sposobem, który zapewniamy w placach zabaw, aby czekać na pracę asynchroniczną jest zaimportuj XCPlaygroundframework i ustawXCPlaygroundPage.currentPage.needsIndefiniteExecution = true . Jeśli ta właściwość została ustawiona, po zakończeniu działania źródła najwyższego poziomu placu zabaw zamiast zatrzymywania tamtejszego placu zabaw będziemy nadal obracać główną pętlę uruchamiania, aby kod asynchroniczny miał szansę zostać uruchomiony. Ostatecznie zamkniemy plac zabaw po upływie limitu czasu, który wynosi domyślnie 30 sekund, ale który można skonfigurować, otwierając edytor asystenta i wyświetlając asystenta osi czasu; limit czasu znajduje się w prawym dolnym rogu.

Na przykład w Swift 3 (używając URLSessionzamiast 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

Lub w 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
Wszystko, co jest warte, jest omówione w WWDC 2014 §408: Swift Playgrounds, druga połowa
Chris Conover

3
Warto zauważyć, że od DP4 XCPlaygroundframework jest teraz dostępny również dla iOS Playgrounds.
ikuramedia

4
Zaktualizowana metoda:XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
R Menke

23
Zaktualizowana metoda: import PlaygroundSupportiPlaygroundPage.current.needsIndefiniteExecution = true
SimplGy

48

Ten interfejs API zmienił się ponownie w Xcode 8 i został przeniesiony do PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

Ta zmiana została wspomniana w sesji 213 na WWDC 2016 .


2
Nie zapomnij zadzwonić PlaygroundPage.current.finishExecution().
Glenn,

36

Od XCode 7.1 XCPSetExecutionShouldContinueIndefinitely()jest przestarzały. Prawidłowym sposobem na to teraz jest zażądanie nieokreślonego wykonania jako właściwości bieżącej strony:

import XCPlayground

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

… Następnie wskaż, kiedy zakończyło się wykonywanie:

XCPlaygroundPage.currentPage.finishExecution()

Na przykład:

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

Powodem, dla którego wywołania zwrotne nie są wywoływane, jest to, że RunLoop nie działa w Playground (lub w trybie REPL).

Nieco dziwacznym, ale skutecznym sposobem na wywołanie funkcji zwrotnych jest użycie flagi, a następnie ręczne iterowanie w pętli uruchamiania:

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

Ten wzorzec był często używany w testach jednostkowych, które muszą testować asynchroniczne wywołania zwrotne, na przykład: wzorzec do testów jednostkowych kolejka asynchroniczna, która wywołuje kolejkę główną po zakończeniu


8

Nowe API jak dla XCode8, Swift3 i iOS 10 to:

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

Uwagi:

Powiedz kompilatorowi, że plik placu zabaw wymaga „nieokreślonego wykonania”

Ręcznie zakończ wykonywanie za pomocą wywołania PlaygroundSupport.current.completeExecution()w programie obsługi zakończenia.

Możesz napotkać problemy z katalogiem pamięci podręcznej i aby je rozwiązać, będziesz musiał ręcznie ponownie utworzyć instancję singletona UICache.shared.

Przykład:

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
NSURLConnection.sendAsynchronousRequest(...)    
NSRunLoop.currentRunLoop().run()
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.