Article

Processing URL Session Data Task Results with Combine URLセッションデータタスク結果をCombineで処理する

Use a chain of asynchronous operators to receive and process data fetched from a URL. あるURLから取って来たデータを受け取りそして処理するために、非同期演算子の連鎖を使ってください。

Overview 概要

Performing tasks with URL sessions is inherently asynchronous; it takes time to fetch data from network endpoints, file systems, and other URL-based sources. The URL Loading System accounts for this by delivering results asynchronously to delegates or completion handlers. The Combine framework also handles asynchronicity; using it to process your URL task results simplifies and empowers your code. URLセッションを伴うタスクの実行は、本質的に非同期です;それはデータをネットワークエンドポイント、ファイルシステム、および他のURL基盤ソースから取ってくるのに時間がかかります。URLローディングシステムは、これに責任を持ちます、結果を非同期に委任先または完了ハンドラに配達することによって。Combineフレームワークもまた非同期にうまく処理します;それを使ってあなたのURLタスク結果それらを平易に処理して、そしてあなたのコードの能力を高めてください。

Create a Data Task Publisher データタスクパブリッシャーを作成する

URLSession offers a Combine publisher, URLSession.DataTaskPublisher, which publishes the results of fetching data from a URL or URLRequest. You create this publisher with the method dataTaskPublisher(for:). When the task completes, it publishes either: URLSessionはCombineパブリッシャー、URLSession.DataTaskPublisherを提供します、それはデータをURLまたはURLRequestから取って来ることの結果を出版(パブリッシュ)します。あなたは、このパブリッシャーをメソッドdataTaskPublisher(for:)で作成します。タスクが完了する時、それが出版するのはこのどちらかです:

  • A tuple that contains the fetched data and a URLResponse, if the task succeeds. あるタプル、それは取って来たデータとURLResponseを含みます、もしタスクが成功するならば。

  • An error, if the task fails. あるエラー、もしタスクが失敗するならば。

Unlike the completion handler passed to dataTask(with:completionHandler:), the types received by your code aren’t optionals, since the publisher has already unwrapped the data or error. dataTask(with:completionHandler:)に渡された完了ハンドラとは違い、あなたのコードによって受け取られる型はオプショナルではありません、パブリッシャーが既にアンラップされたデータまたはエラーを持つことから。

When using URLSession’s completion handler-based code, you need to do all your work in the handler closure: error-handling, data parsing, and so on. When you instead use the data task publisher, you can move many of these responsibilities to Combine operators. URLSessionのもつ完了ハンドラ基盤のコードを使う場合、あなたはあなたの仕事を全てハンドラクロージャにおいて行う必要があります:エラー処理、データ解析、など。あなたが代わりにデータタスクパブリッシャーを使う場合、あなたはこれらの責任の多くをCombine演算子へと移せます。

Convert Incoming Raw Data to Your Types with Combine Operators やって来る生の値をあなたの型へとCombineの演算子それらで変換する

When a data task completes successfully, it delivers a block of raw Data to your app. Most apps need to convert this data to their own types. Combine provides operators to perform these conversions, allowing you to declare a chain of processing operations. データタスクがうまく完了する場合、それは生のDataのある塊をあなたのアプリに配達します。ほとんどのアプリは、このデータをそれら独自の型へと変換する必要があります。Combineは、これら変換を実行する演算子を提供して、あなたに演算処理の連鎖を宣言可能にしています。

The data task publisher produces a tuple that contains a Data and a URLResponse. You can use the map(_:) operator to convert the contents of this tuple to another type. If you want to inspect the response before inspecing the data, use tryMap(_:) and throw an error if the response is unacceptable. データタスクパブリッシャーは、あるタプルを生成します、それはあるDataとあるURLResponseを含みます。あなたは、map(_:)演算子を使って、このタプルの内容を別の型へと変換できます。あなたがデータを調査する前にその応答を調査したいならば、tryMap(_:)を使ってください、そしてその応答が受け入れられないものならばエラーをスローしてください。

To convert raw data to your own types that conform to the Decodable protocol, use Combine’s decode(type:decoder:) operator. 生の値をDecodableプロトコルに準拠するあなた独自の型へと変換するには、Combineのもつdecode(type:decoder:)演算子を使ってください。

The following example combines both these operators to parse JSON data from a URL endpoint into a custom User type: 以下の例は、これら演算子の両方を結合して、あるURLからのJSONデータをあるあつらえのUser型へと構文解析します:


struct User: Codable {
    let name: String
    let userID: String
}
let url = URL(string: "https://example.com/endpoint")!
cancellable = urlSession
    .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: User.self, decoder: JSONDecoder())
    .sink(receiveCompletion: { print ("Received completion: \($0).") },
          receiveValue: { user in print ("Received user: \(user).")})

Retry Transient Errors and Catch and Replace Persistent Errors 一時的エラーを再試行するそして永続的エラーを捉えて置き換える

Any app that uses the network should expect to encounter errors, and your app should handle them gracefully. Because transient network errors are fairly common, you may want to immediately retry a failed data task. With URLSession’s completion handler idiom, you need to create a whole new task to perform a retry. With the data task publisher, you can instead use Combine’s retry(_:) operator. This handles errors by recreating the subscription to the upstream publisher a specified number of times. However, since network operations are expensive, only retry a small number of times, and ensure all requests are idempotent. ネットワークを使うあらゆるアプリは、出くわすエラーを予測すべきです、そしてあなたのアプリはそれらを美しく取り扱うべきです。一時的ネットワークエラーはかなり普通であることから、あなたは失敗したデータタスクを直ぐに再試行したいかもしれません。URLSessionのもつ完了ハンドラ慣用句では、あなたは再試行を実行する新しいタスク全体を作成する必要があります。データタスクパブリッシャーでは、あなたは代わりにCombineのもつretry(_:)演算子を使用できます。これは、ある指定された回数アップストリームパブリッシャーへのサブスクリプションを再度作成することによってエラーを取り扱います。しかしながら、ネットワーク演算が高くつくことから、少ない回数を再試行するだけにしてください、そして全ての要請がべき等であるのを確実にしてください。

You can also use Combine operators to replace the error, rather than letting it reach the subscriber: あなたはまたCombine演算子それらを使ってエラーを置き換え可能です、それが加入者に届くのを許すのではなく:

  • catch(_:) replaces the error with another publisher. You can use this with another URLSession.DataTaskPublisher, such as one that loads data from a fallback URL. catch(_:)は、エラーを別のパブリッシャーで置き換えます。あなたは、これを別のURLSession.DataTaskPublisher、たとえばデータを予備URLからロードするものとともに使用できます。

  • replaceError(with:) replaces the error with an element you provide. If it makes sense in your application, you can use this to provide a substitute for the value you expected to load from the URL. replaceError(with:)は、エラーをあなたが提供する要素と置き換えます。それがあなたのアプリケーションにおいて意味をなすならば、あなたはこれを使ってある代用品を、あなたがそのURLからロードするつもりだった値に対して提供できます。

The following example shows both of these techniques, retrying a failed request once, and using a fallback URL after that. If either the original request, the retry, or the fallback request succeeds, the sink(receiveValue:) operator receives data from the endpoint. If all three fail, the sink receives a Subscribers.Completion.failure(_:). 以下の例は、これらテクニックの両方、失敗したリクエストをもう一度再試行すること、そして予備URLをその後で使うことを示します。元のリクエスト、再試行、または予備リクエストのどれかが成功するならば、sink(receiveValue:)演算子はデータをエンドポイントから受け取ります。3つ全てが失敗するならば、sinkは、Subscribers.Completion.failure(_:)を受け取ります。


let pub = urlSession
    .dataTaskPublisher(for: url)
    .retry(1)
    .catch() { _ in
        self.fallbackUrlSession.dataTaskPublisher(for: fallbackURL)
    }
cancellable = pub
    .sink(receiveCompletion: { print("Received completion: \($0).") },
          receiveValue: { print("Received data: \($0.data).") })

Move Work Between Dispatch Queues With Scheduling Operators 作業をディスパッチキューの間でスケジューリング演算子をつかって移動する

When using URLSession’s delegate and completion handler idioms, the session calls back to your code on a fixed delegateQueue. Sometimes, this means your callback code has to manually use dispatch queues or other scheduling APIs to put work on a specific queue. URLSessionのもつ委任先と完了ハンドラ慣用句を使っている場合、セッションは逆にあなたのコードをdelegateQueue上で呼び出します。時々、これはあなたのコールバックコードが手動でディスパッチキューや他のスケジューリングAPIを使って仕事を特定のキュー上に置かなければならないことを意味します。

With URLSession.DataTaskPublisher, you can use Combine’s scheduling operators instead. Use receive(on:options:) to specify how you want later operators in the chain and your subscriber, to schedule the work. DispatchQueue and RunLoop both implement Combine’s Scheduler protocol, so you can use them to receive URL session data. The following snippet ensures that the sink logs its results on the main dispatch queue. URLSession.DataTaskPublisherでは、あなたはCombineのもつスケジューリング演算子を代わりに使用できます。receive(on:options:)を使ってください、どのくらいあなたがより後に演算子それらをある連鎖において望むかを指定するために、仕事を予定するために。DispatchQueueおよびRunLoop両方ともCombineのもつSchedulerプロトコルを実装します、なのであなたはそれらを使ってURLセッションデータを受け取ることができます。以下の小片は、sinkがそれの結果をメインディスパッチキュー上で記録することを確実にします。


cancellable = urlSession
    .dataTaskPublisher(for: url)
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { print ("Received completion: \($0).") },
          receiveValue: { print ("Received data: \($0.data).")})

Share the Result of a Data Task Publisher with Multiple Subscribers データタスクパブリッシャーの結果を複数の加入者で共有する

You may want to use data from the URL endpoint in different parts of your application. Because network requests are expensive, don’t reissue them needlessly. Combine lets you use multiple subscribers to a single URLSession.DataTaskPublisher, while allowing the publisher to service all of them with a single request. あなたは、URLエンドポイントからのデータをあなたのアプリケーションの異なる部分において使いたいと望むかもしれません。ネットワークリクエストは高くつくことから、それらを必要もないのに再度発令しないでください。Combineは、あなたに複数の加入者を単一のURLSession.DataTaskPublisherに扱わせます、一方でそのパブリッシャーが彼らの全員に単一のリクエストでサービスを提供できるようにします。

To support multiple downstream subscribers, use the share() operator. This operator works like a combination of the Publishers.Multicast and PassthroughSubject publishers. You can connect multiple operator chains or subscribers to the share() operator, and any upstream publisher only sees one downstream. In the case of a URLSession.DataTaskPublisher, this means it only performs the data task once. 複数のダウンストリーム加入者をサポートするには、share()演算子を使ってください。この演算子は、Publishers.MulticastPassthroughSubjectパブリッシャーの結合物のように働きます。あなたは、複数の演算子連鎖や加入者をshare()演算子に接続できます、そしてどんなアップストリームパブリッシャーも1つのダウンストリームを見るだけです。URLSession.DataTaskPublisherの場合において、これは、それがデータタスクを一度実行するだけであるのを意味します。

The following example uses a URL session data task for two unrelated purposes. One subscriber uses the returned data to parse the custom User type seen earlier, and logs it on the main dispatch queue. A second subscriber is only concerned with the URLResponse, which it inspects to print an HTTP status code, and doesn’t care which queue it uses. By using share(), the data task publisher can serve both subscribers with a single load from the URL endpoint. 以下の例は、URLセッションデータタスクを2つの関係のない目的に使います。一方の加入者は、返されるデータを使って、前に見たあつらえのUser型を解析します、そしてそれをメインディスパッチキュー上で記録します。2番目の加入者は、それがHTTP状態コードを出力するために調査するURLResponseに関心があるだけです、そしてどのキューをそれが使うのかは気にしません。share()を使うことによって、データタスクパブリッシャーは、両方の加入者にURLエンドポイントからのある単一のロードで奉仕できます。


let sharedPublisher = urlSession
    .dataTaskPublisher(for: url)
    .share()


cancellable1 = sharedPublisher
    .tryMap() {
        guard $0.data.count > 0 else { throw URLError(.zeroByteResource) }
        return $0.data
    }
    .decode(type: User.self, decoder: JSONDecoder())
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { print ("Received completion 1: \($0).") },
          receiveValue: { print ("Received id: \($0.userID).")})


cancellable2 = sharedPublisher
    .map() {
        $0.response
    }
    .sink(receiveCompletion: { print ("Received completion 2: \($0).") },
           receiveValue: { response in
            if let httpResponse = response as? HTTPURLResponse {
                print ("Received HTTP status: \(httpResponse.statusCode).")
            } else {
                print ("Response was not an HTTPURLResponse.")
            }
    }
)

To prove that this code only loads the data once, temporarily put a print(_:to:) debugging operator before the share() operator. When the app runs, Xcode’s console output shows it receives only a single value from the data task publisher, even though both subscribers receive their expected results. このコードがデータを一度ロードするだけであることを立証するには、一時的にprint(_:to:)デバッギング演算子をshare()演算子の前に置いてください。アプリが動作する時、Xcodeのもつコンソール出力は、それがある単一の値だけをデータタスクパブリッシャーから受け取ることを示します、たとえ両方の加入者が彼らの期待する結果を受け取るとしてもです。

Be aware that the URL session starts loading data as soon as the URLSession.DataTaskPublisher has unsatisfied demand from a downstream subscriber. In this case, that happens when the first sink subscriber attaches. If you need extra time to attach other subscribers, use makeConnectable() to wrap the Publishers.Share publisher with a ConnectablePublisher. After connecting all subscribers, call connect() on the connectable publisher to allow the data load to begin. URLSession.DataTaskPublisherがダウンストリーム加入者からの満たされない要求を持つやいなや、URLセッションがデータのロードを始めることを承知していてください。この場合では、それが起こるのは最初のsink加入者が所属する時です。あなたが他の加入者を所属させるのに余分な時間が必要ならば、makeConnectable()を使ってPublishers.ShareパブリッシャーをConnectablePublisherでラップしてください。全ての加入者が接続する後、connect()を接続可能パブリッシャー上で呼び出して、データロードの開始を許可してください。

See Also 参照

Performing Tasks as a Combine Publisher タスクをCombineパブリッシャーとして実行する