Swift Playgroundsで非同期処理の完了を待つ方法

3 min read

ちょとしたSwiftのコードを書きたいときにmacOS版のSwift Playgroundsを使っています。

https://www.apple.com/jp/swift/playgrounds/

それなりに使いやすいのですが、非同期処理を書くと実行が完了する前にプログラムが終了してしまうので困っていました。

例えば、以下のようなURLSessionを使ってGitHubのリポジトリ数を取得するコードを実行すると、最後の

code completed.

とだけ表示されてプログラムが終了してしまいます。

import Combine
import Foundation

struct SearchResponse: Decodable {
    var totalCount: Int
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

let url = URL(string: "https://api.github.com/search/repositories?q=swift")!
var cancellable = URLSession.shared.dataTaskPublisher(for: url)
    .tryMap() { element -> Data in
        guard let httpResponse = element.response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return element.data
    }
    .decode(type: SearchResponse.self, decoder: decoder)
    .sink(receiveCompletion: { print ("Received completion: \($0).") },
          receiveValue: { repositories in print ("Received: \(repositories.totalCount).")})

print("code completed.")

調べたところ、PlaygroundPageneedsIndefiniteExecutiontrueにすると、プログラムが終了せず無期限で実行されるようになるので、結果的に非同期処理が行われるようになります。

https://developer.apple.com/documentation/playgroundsupport/playgroundpage/1964501-needsindefiniteexecution

具体的にはPlaygroundsに以下のコードを追加します。

import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

先程のコードを例にすると、先頭に上記のコードを追加して実行します。

import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

import Combine
import Foundation

struct SearchResponse: Decodable {
    var totalCount: Int
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

let url = URL(string: "https://api.github.com/search/repositories?q=swift")!
var cancellable = URLSession.shared.dataTaskPublisher(for: url)
    .tryMap() { element -> Data in
        guard let httpResponse = element.response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return element.data
    }
    .decode(type: SearchResponse.self, decoder: decoder)
    .sink(receiveCompletion: { print ("Received completion: \($0).") },
          receiveValue: { repositories in print ("Received: \(repositories.totalCount).")})

print("code completed.")

今度は以下のように出力されて非同期処理の実行結果が出力されました。

code completed.
Received: 209193.
Received completion: finished.

このままでは「停止」ボタンを押すかエディタで編集をするまでプログラムが終了しないので、明示的に終了させるために処理の完了後にfinishExecutionを実行します。

https://developer.apple.com/documentation/playgroundsupport/playgroundpage/1964505-finishexecution

なお、PlaygroundPageが利用できる環境は以下の通りです。

macOS 11.0+
Xcode 10.2+
Swift Playgrounds 2.0+

https://developer.apple.com/documentation/playgroundsupport/playgroundpage