---
description: 負責透過 URL 到網路抓資料的 Publisher。
---

# URLSession.DataTaskPublisher

## 參考資料

{% tabs %}
{% tab title="🍎 官方" %}
* [Foundation](https://developer.apple.com/documentation/foundation) > [URL Loading System](https://developer.apple.com/documentation/foundation/url_loading_system) > [URLSession](https://developer.apple.com/documentation/foundation/urlsession)
  * [URLSession.DataTaskPublisher](https://developer.apple.com/documentation/foundation/urlsession/datataskpublisher)
  * [Processing URL Session Data Task Results with Combine](https://developer.apple.com/documentation/foundation/urlsession/processing_url_session_data_task_results_with_combine) ⭐
{% endtab %}

{% tab title="📘 參考" %}

{% endtab %}

{% tab title="👥  相關" %}
* [❌ escaping closure captures mutating 'self' parameter](../../../../../../compiler-errors/escaping-closure-captures-mutating-self-parameter)
{% endtab %}
{% endtabs %}

## 將 Data 轉為 Swift 型別

下面的範例示範如何從網路擷取 JSON 資料，然後轉為自己的 Swift 型別：\
👉  [Processing URL Session Data Task Results with Combine](https://developer.apple.com/documentation/foundation/urlsession/processing_url_session_data_task_results_with_combine) - Convert Incoming Raw Data to Your Types

{% tabs %}
{% tab title="💈 範例" %}
```swift
import Foundation  // URL
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

// consoles
var con = ""
let console = PlaygroundConsole.default    // 🐶 PlaygroundConsole

// Post
struct Post: Codable {
    let userId: Int
    let id    : Int
    let title : String
    let body  : String
}

// 🌀 URL extension
let posts: URL = "http://jsonplaceholder.typicode.com/posts"

let cancellable = URLSession.shared

    // ⭐️ produces a tuple: (data: Data, response: URLResponse)
    .dataTaskPublisher(for: posts)
    
    // ⭐️ use map(_:) to convert contents of tuple to another type.
    // ⭐️ use tryMap(_:) to inspect `response` before inspecing `data`,
    //    throw an error if the response is unacceptable.
    .tryMap() { tuple -> Data in
        guard 
            let httpResponse = tuple.response as? HTTPURLResponse,
            httpResponse.statusCode == 200 
            else { throw URLError(.badServerResponse) }
        return tuple.data 
    }
        
    // ⭐️ convert data to their own types
    .decode(type: [Post].self, decoder: JSONDecoder())
    
    // ⭐️ receive final results
    .sink(receiveCompletion: { 
        print ("Received completion: \($0).", to: &con); con 
    }, receiveValue: { 
        print ("Received posts: \($0.prefix(3)).", to: &con); con 
        print("posts: ", $0.count); console
    })
```
{% endtab %}

{% tab title="🌀 套件" %}
* [🐶 PlaygroundConsole](../../../../../../master/todo/custom-types/playgroundconsole)
* [🌀  URL](../../foundation/url/url)
{% endtab %}

{% tab title="👥  相關" %}
* [{JSON} Placeholder](http://jsonplaceholder.typicode.com)
{% endtab %}
{% endtabs %}

## 錯誤處理

* 暫時的錯誤可用 .**retry**(\_:) 處理。
* 無法排除的錯誤可用 .**catch**(\_:) 與 .**replaceError**(with:) 處理

{% tabs %}
{% tab title="💈 範例" %}
```swift
/*
 * Retry Transient Errors
 * Catch and Replace Persistent Errors
 * -----------------------------------
 */

let fallbackURL: URL = "some fallback url"
let fallbackSession = URLSession.shared

session
    .dataTaskPublisher(for: url)
    
    // ⭐️ transient errors:
    .retry(1)
    
    // ⭐️ persistent errors:
    //    use catch(_:) to replace error with another publisher
    //    use replaceError(with:) to replace error with an element you provide
    .catch() { _ in
        fallbackSession.dataTaskPublisher(for: fallbackURL)  
    }
    
    .sink(receiveCompletion: { 
        print("Received completion: \($0).") 
    }, receiveValue: { 
        print("Received data: \($0.data).") 
    })
```
{% endtab %}

{% tab title="Second Tab" %}

{% endtab %}
{% endtabs %}

## Scheduling Operators

{% tabs %}
{% tab title="💈 範例" %}
```swift
/*
 * Move Work Between Dispatch Queues With Scheduling Operators
 * -----------------------------------------------------------
 */

session
    .dataTaskPublisher(for: url)
    
    // ⭐️ specify how you want later operators in the chain and your subscriber,
    //    to schedule the work
    .receive(on: DispatchQueue.main)
    
    .sink(receiveCompletion: { 
        print ("Received completion: \($0).") 
    }, receiveValue: { 
        print ("Received data: \($0.data).")
    })
```
{% endtab %}

{% tab title="Second Tab" %}

{% endtab %}
{% endtabs %}

## 分享網路擷取資料

{% tabs %}
{% tab title="💈 範例" %}
```swift
/*
 * Share Result with Multiple Subscribers
 * --------------------------------------
 */

var set = Set<AnyCancellable>()

let shared = session
    .dataTaskPublisher(for: url)
    
    // ⭐️ use share() to support multiple downstream subscribers
    .share()

shared
    .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).")
    }).store(in: &set)

shared
    .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.")
        }
    }).store(in: &set)
```
{% endtab %}

{% tab title="Second Tab" %}

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
⚠️  注意：

* 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.&#x20;
{% endhint %}
