[asynchronous] Playground에서 비동기 콜백을 실행하는 방법

많은 Cocoa 및 CocoaTouch 메서드에는 Objective-C의 블록 및 Swift의 Closures로 구현 된 완료 콜백이 있습니다. 그러나 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)
    }
}

내 플레이 그라운드 타임 라인에서 콘솔 출력을 볼 수 있지만 println완료 블록은 호출되지 않습니다.



답변

런 루프를 수동으로 실행할 수 있지만 (또는 런 루프가 필요하지 않은 비동기 코드의 경우 디스패치 세마포와 같은 다른 대기 방법을 사용) 비동기 작업을 기다리기 위해 플레이 그라운드에서 제공하는 “내장”방식은 다음과 같습니다. 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


답변

이 API는 Xcode 8에서 다시 변경되었으며 다음으로 이동되었습니다 PlaygroundSupport.

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

이 변경 사항은 WWDC 2016의 세션 213에서 언급되었습니다 .


답변

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


답변

콜백이 호출되지 않는 이유는 RunLoop이 Playground (또는 REPL 모드)에서 실행되지 않기 때문입니다.

다소 불안정하지만 효과적인 콜백을 작동시키는 방법은 플래그를 사용하고 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)
}

이 패턴은 비동기 콜백을 테스트해야하는 단위 테스트에서 자주 사용되었습니다. 예 : 완료시 기본 큐를 호출하는 비동기 큐를 단위 테스트하는 패턴


답변

XCode8, Swift3 및 iOS 10의 새로운 API는 다음과 같습니다.

// import the module
import PlaygroundSupport
// write this at the beginning
PlaygroundPage.current.needsIndefiniteExecution = true
// To finish execution
PlaygroundPage.current.finishExecution()


답변

스위프트 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, xcode 8, iOS 10

노트:

컴파일러에게 플레이 그라운드 파일에 “무한 실행”이 필요함을 알립니다.

PlaygroundSupport.current.completeExecution()완료 핸들러 내 에서를 호출하여 수동으로 실행을 종료합니다.

캐시 디렉토리에 문제가 발생할 수 있으며이 문제를 해결하려면 UICache.shared 싱글 톤을 수동으로 다시 인스턴스화해야합니다.

예:

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