遊び場で非同期コールバックを実行するにはどうすればよいですか
-
26-12-2019 - |
質問
多くのCocoaとCocoAtouchメソッドには、Objective-CのブロックとSwiftのクロージャとして実装された完了コールバックがあります。ただし、これらを遊び場で試してみると、完成は決して呼ばれません。例えば:
// 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
を設定することです。このプロパティが設定されている場合、最上位プレイグラウンドソースが終了すると、そこでプレイグラウンドを停止するのではなく、メインランループを回転させ続けるので、非同期コードには実行する機会があります。デフォルトのタイムアウト後に遊び場を終了しますが、アシスタントエディタを開き、タイムラインアシスタントを表示すると設定できます。タイムアウトは右下にあります。
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
. 他のヒント
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()
. コールバックが呼び出されていない理由は、ランループが遊び場で実行されていない(またはその問題のためにRelp Mode)からです。
幾分jankyですが、コールバックを動作させるための効果的な方法はフラグを持ち、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)
}
.
このパターンは、例えば、非同期コールバックをテストする必要がある単位テストでよく使用されています。メインキューを呼び出す非同期キューのためのパターン
Xcode 8、Swift 3、IOS 10は、
です。// import the module
import PlaygroundSupport
// write this at the beginning
PlaygroundPage.current.needsIndefiniteExecution = true
// To finish execution
PlaygroundPage.current.finishExecution()
. 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()
. Swift 3、Xcode 8、iOS 10
ノート:
遊具ファイルが「不定実行」
を必要とするようにコンパイラに指示する完了ハンドラ内のPlaygroundSupport.current.completeExecution()
への呼び出しを介して手動で実行を終了します。
キャッシュディレクトリに問題が発生し、これを解決することができます。
例:
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()
.