web-dev-qa-db-ja.com

プレイグラウンドで非同期コールバックを実行する方法

多くの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は呼び出されません...

114
ikuramedia

実行ループを手動で実行できますが(または、実行ループを必要としない非同期コードの場合は、ディスパッチセマフォなどの他の待機メソッドを使用します)、プレイグラウンドで非同期作業を待機する「組み込み」方法は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
182
Rick Ballard

このAPIはXcode 8で再び変更され、PlaygroundSupportに移動されました。

import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

この変更は WWDC 2016のセッション21 で言及されました。

47
BalestraPatrick

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()
36
Paul Cantrell

コールバックが呼び出されない理由は、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)
}

このパターンは、非同期コールバックをテストする必要があるユニットテストでよく使用されます。たとえば、 完了時にメインキューを呼び出すユニットテスト非同期キューのパターン

15
ikuramedia

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

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()
5
p-sun

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()
3
Lloyd Briggs