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