Playgroundで非同期コールバックを実行する方法


117

多くのCocoaおよびCocoaTouchメソッドには、Objective-CのブロックおよびSwiftのClosuresとして実装された完了コールバックがあります。ただし、プレイグラウンドでこれらを試してみると、完了が呼び出されることはありません。例えば:

// 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、完了ブロックのは呼び出されません...

回答:


186

手動で実行ループを実行できます(または、実行ループを必要としない非同期コードの場合は、ディスパッチセマフォなどの他の待機メソッドを使用します)。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

1
それは価値があるすべてのために、これはWWDC 2014§408でカバーされています:スイフトプレイグラウンド、後半
Chris Conover 14

3
DP4以降、XCPlaygroundフレームワークがiOSプレイグラウンドでも利用できるようになったことは注目に値します。
ikuramedia 2014

4
更新された方法:XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
R Menke 2015年

23
更新されたメソッド:import PlaygroundSupportおよびPlaygroundPage.current.needsIndefiniteExecution = true
SimplGy 2016年


36

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

16

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

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


8

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

5

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

3

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.