多くの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)
}
}
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のセッション21 で言及されました。
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がプレイグラウンド(またはその点で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()
Swift 4、Xcode 9.
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()
を呼び出して、実行を手動で終了します。
キャッシュディレクトリで問題が発生する可能性があります。これを解決するには、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()