ฉันจะเรียกใช้การเรียกกลับแบบอะซิงโครนัสใน Playground ได้อย่างไร


117

วิธีการ Cocoa และ CocoaTouch จำนวนมากมีการเรียกกลับที่สมบูรณ์ซึ่งนำไปใช้เป็นบล็อกใน Objective-C และ Closures ใน Swift อย่างไรก็ตามเมื่อลองใช้สิ่งเหล่านี้ใน Playground จะไม่มีการเรียกความสมบูรณ์ ตัวอย่างเช่น:

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

ฉันสามารถเห็นเอาต์พุตคอนโซลในไทม์ไลน์ Playground ของฉัน แต่printlnในบล็อกเสร็จสิ้นของฉันจะไม่ถูกเรียก ...

คำตอบ:


186

ในขณะที่คุณสามารถรัน run loop ด้วยตนเอง (หรือสำหรับโค้ดอะซิงโครนัสที่ไม่ต้องใช้ run loop ให้ใช้วิธีการรออื่น ๆ เช่น dispatch semaphores) วิธี "ในตัว" ที่เราจัดเตรียมไว้ใน Playgrounds เพื่อรอการทำงานแบบอะซิงโครนัสคือการ นำเข้ากรอบและการตั้งค่าXCPlayground XCPlaygroundPage.currentPage.needsIndefiniteExecution = trueหากคุณสมบัตินี้ได้รับการตั้งค่าเมื่อแหล่งสนามเด็กเล่นระดับบนสุดของคุณเสร็จสิ้นแทนที่จะหยุดสนามเด็กเล่นที่นั่นเราจะหมุนลูปรันหลักต่อไปดังนั้นโค้ดอะซิงโครนัสจึงมีโอกาสทำงานได้ ในที่สุดเราจะยุติสนามเด็กเล่นหลังจากหมดเวลาซึ่งมีค่าเริ่มต้นเป็น 30 วินาที แต่สามารถกำหนดค่าได้หากคุณเปิดตัวแก้ไขผู้ช่วยและแสดงตัวช่วยไทม์ไลน์ หมดเวลาอยู่ที่ด้านขวาล่าง

ตัวอย่างเช่นใน Swift 3 (ใช้URLSessionแทน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

หรือใน 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
สำหรับทุกสิ่งที่คุ้มค่านี้ครอบคลุมใน WWDC 2014 §408: Swift Playgrounds ครึ่งหลัง
Chris Conover

3
ที่น่าสังเกตว่าจาก DP4 XCPlaygroundตอนนี้เฟรมเวิร์กพร้อมใช้งานสำหรับ iOS Playgrounds
ikuramedia

4
อัปเดตวิธีการ:XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
R Menke

23
ปรับปรุงวิธีการ: import PlaygroundSupportและPlaygroundPage.current.needsIndefiniteExecution = true
SimplGy

48

API นี้เปลี่ยนอีกครั้งใน Xcode 8 และถูกย้ายไปที่PlaygroundSupport:

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

การเปลี่ยนแปลงนี้ได้รับการกล่าวถึงในเซสชั่น 213 ที่ WWDC 2016


2
อย่าลืมโทรPlaygroundPage.current.finishExecution().
Glenn

36

ในขณะที่ XCode 7.1 XCPSetExecutionShouldContinueIndefinitely()เลิกใช้งานแล้ว วิธีที่ถูกต้องในการดำเนินการในตอนนี้คือขั้นแรกขอให้ดำเนินการไม่ จำกัด เป็นคุณสมบัติของหน้าปัจจุบัน:

import XCPlayground

XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

…จากนั้นระบุเมื่อการดำเนินการเสร็จสิ้นด้วย:

XCPlaygroundPage.currentPage.finishExecution()

ตัวอย่างเช่น:

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

สาเหตุที่ไม่มีการเรียกกลับเนื่องจาก RunLoop ไม่ทำงานใน Playground (หรือในโหมด REPL สำหรับเรื่องนั้น)

วิธีที่ค่อนข้างหยาบคาย แต่มีประสิทธิภาพในการทำให้การเรียกกลับดำเนินการคือการใช้แฟล็กจากนั้นทำซ้ำด้วยตนเองบนรันลูป:

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

รูปแบบนี้มักใช้ในการทดสอบหน่วยซึ่งจำเป็นต้องทดสอบการเรียกกลับแบบ async เช่นรูปแบบสำหรับการทดสอบหน่วย async คิวที่เรียกคิวหลักเมื่อเสร็จสิ้น


8

API ใหม่สำหรับ XCode8, Swift3 และ iOS 10 ได้แก่

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

หมายเหตุ:

บอกคอมไพเลอร์ว่าไฟล์ Playground ต้องการ "การดำเนินการที่ไม่มีกำหนด"

ยุติการดำเนินการด้วยตนเองผ่านการโทรไปยังPlaygroundSupport.current.completeExecution()ภายในตัวจัดการที่สมบูรณ์ของคุณ

คุณอาจประสบปัญหากับไดเรกทอรีแคชและในการแก้ไขปัญหานี้คุณจะต้องสร้างอินสแตนซ์ UICache.shared singleton ด้วยตนเอง

ตัวอย่าง:

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.