플레이그라운드에서 비동기 콜백을 어떻게 실행하나요?
-
26-12-2019 - |
문제
많은 Cocoa 및 CocoaTouch 메소드에는 Objective-C 및 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
내 완료 블록에서는 호출되지 않습니다 ...
해결책
런 루프를 수동으로 실행할 수 있지만(또는 런 루프가 필요하지 않은 비동기 코드의 경우 디스패치 세마포어와 같은 다른 대기 방법을 사용) 비동기 작업을 기다리기 위해 플레이그라운드에 제공하는 "내장" 방식은 다음과 같습니다. 수입하다 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()
NSURLConnection.sendAsynchronousRequest(...)
NSRunLoop.currentRunLoop().run()