Bagaimana cara menjalankan panggilan balik Asynchronous di Playground

117

Banyak metode Cocoa dan CocoaTouch memiliki callback penyelesaian yang diimplementasikan sebagai blok di Objective-C dan Closures di Swift. Namun, saat mencobanya di Playground, penyelesaiannya tidak pernah dibatalkan. Sebagai contoh:

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

Saya dapat melihat output konsol di timeline Playground saya, tetapi printlnblok penyelesaian saya tidak pernah dipanggil ...

ikuramedia
sumber

Jawaban:

186

Meskipun Anda dapat menjalankan run loop secara manual (atau, untuk kode asinkron yang tidak memerlukan run loop, gunakan metode tunggu lain seperti semaphore pengiriman), cara "bawaan" yang kami sediakan di taman bermain untuk menunggu pekerjaan asinkron adalah dengan impor XCPlaygroundkerangka dan set XCPlaygroundPage.currentPage.needsIndefiniteExecution = true. Jika properti ini telah disetel, saat sumber taman bermain tingkat atas Anda selesai, alih-alih menghentikan taman bermain di sana, kami akan terus memutar putaran proses utama, sehingga kode asinkron memiliki peluang untuk dijalankan. Kami akhirnya akan menghentikan taman bermain setelah batas waktu yang defaultnya 30 detik, tetapi yang dapat dikonfigurasi jika Anda membuka asisten editor dan menampilkan asisten garis waktu; batas waktu ada di kanan bawah.

Misalnya, di Swift 3 (menggunakan URLSessionalih-alih 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

Atau di 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
Rick Ballard
sumber
1
Untuk semua nilainya, Ini tercakup dalam WWDC 2014 §408: Swift Playgrounds, babak kedua
Chris Conover
3
Perlu dicatat bahwa dari DP4 XCPlaygroundkerangka sekarang tersedia untuk iOS Playgrounds juga.
ikuramedia
4
Metode yang diperbarui:XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
R Menke
23
Metode yang diperbarui: import PlaygroundSupportdanPlaygroundPage.current.needsIndefiniteExecution = true
SimplGy
48

API ini berubah lagi di Xcode 8 dan dipindahkan ke PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

Perubahan ini disebutkan dalam Sesi 213 di WWDC 2016 .

BalestraPatrick
sumber
2
Jangan lupa menelepon PlaygroundPage.current.finishExecution().
Glenn
36

Pada XCode 7.1, XCPSetExecutionShouldContinueIndefinitely()sudah tidak digunakan lagi. Cara yang benar untuk melakukannya sekarang adalah dengan terlebih dahulu meminta eksekusi tak terbatas sebagai properti halaman saat ini:

import XCPlayground

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

… Lalu tunjukkan saat eksekusi telah selesai dengan:

XCPlaygroundPage.currentPage.finishExecution()

Sebagai contoh:

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

Alasan callback tidak dipanggil adalah karena RunLoop tidak berjalan di Playground (atau dalam mode REPL).

Cara yang agak janky, tetapi efektif, untuk membuat callback beroperasi adalah dengan sebuah flag dan kemudian melakukan iterasi secara manual pada 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)
}

Pola ini sering digunakan dalam Pengujian Unit yang perlu menguji callback asinkron, misalnya: Pola untuk pengujian unit antrean asinkron yang memanggil antrean utama saat penyelesaian

ikuramedia
sumber
8

API baru untuk XCode8, Swift3 dan iOS 10 adalah,

// import the module
import PlaygroundSupport
// write this at the beginning
PlaygroundPage.current.needsIndefiniteExecution = true
// To finish execution
PlaygroundPage.current.finishExecution()
bradd123
sumber
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()
p-sun
sumber
3

Swift 3, xcode 8, iOS 10

Catatan:

Beri tahu kompiler bahwa file taman bermain memerlukan "eksekusi tak terbatas"

Hentikan eksekusi secara manual melalui panggilan ke PlaygroundSupport.current.completeExecution()dalam penangan penyelesaian Anda.

Anda mungkin mengalami masalah dengan direktori cache dan untuk mengatasinya Anda perlu secara manual membuat ulang UICache.shared singleton.

Contoh:

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()
Lloyd Briggs
sumber
-3
NSURLConnection.sendAsynchronousRequest(...)    
NSRunLoop.currentRunLoop().run()
Tony Pan
sumber