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
})
/*
* 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).")
})
Scheduling Operators
/*
* 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).")
})
分享網路擷取資料
/*
* 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)
⚠️ 注意:
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.