Notes: 01. Networking with Combine
Prerequisites: Intermediate Swift knowledge, basic Combine knowledge, knowledge of URLSession
.
URLSession supports a variety of operations such as: data transfer tasks to retrieve contents of a URL; download tasks to retrieve the contents of a URL and save it to a file; upload tasks to upload files and data to a URL; stream tasks to stream data between two parties; websocket tasks to connect to websockets.
Only the first operation - data transfer tasks - expose a Combine publisher, taking either a URL or a URLRequest. Let’s look at that in an example.
Start an example block, and make a URL
by passing in a URL for a JSON file, and if it fails use a guard statement to return
example(of: "dataTaskPublisher") {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1") else {
return
}
Create the Combine pipeline, making sure to keep the resulting subscription; otherwise it will immediately be cancelled and the request will never execute.
On the URLSession.shared
instance, call dataTaskPublisher(for:)
, passing in the url you made earlier.
// 1
URLSession.shared
// 2
.dataTaskPublisher(for: url)
Add a sink to the Combine pipeline, and use the more complex sink(receiveCompletion: receiveValue:)
version, so both errors and received values can be sent to the console. This is very important because networks are prone to failure.
.sink(
receiveCompletion: { completion in
// 3
if case .failure(let err) = completion {
print("Retrieving data failed with error \(err)")
}
}, receiveValue: { data, response in
// 4
print("Retrieved data of size \(data.count), response = \(response)")
})
.store(in: &subscriptions)
The received value in the sink
is a tuple, containing the Data
object and the URLResponse
from the server. This is very similar to the closure used in normal URLSession
use, but with a simpler publisher abstraction from Combine.
Codable is a very powerful protocol in Swift that provides an encoding and decoding mechanism you should definitely be familiar with. If you’re not, be sure to check out the various courses on raywenderlich.com including, but not limited to, “Encoding and Decoding with Swift” by Cosmin Pupaza.
There are several types of encoders and decoders in Foundation, but one pair stands out when it comes to networking - JSONDecoder and JSONEncoder. Let’s continue the URLSession and use JSONDecoder to decode the JSON you downloaded - and then see how we can improve on that with the decode()
operation from Combine.
Create the same subscription as you did in the URLSession example, and add the dataTaskPublisher(for:)
operator.
example(of: "decode") {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1") else {
return
}
URLSession.shared
.dataTaskPublisher(for: url)
Remember that dataTaskPublisher
emits a tuple, so you have to use map to grab only the data part of the tuple. Since JSONDecoder.decode can throw an error, you have to put the try
keyword in front of it, which means that you must use tryMap
and not just map
.tryMap { data, _ in
try JSONDecoder().decode(Todo.self, from: data)
}
Complete the rest of the Combine pipeline with the same sink
as before.
.sink(receiveCompletion: { completion in
if case .failure(let err) = completion {
print("Retrieving data failed with error \(err)")
}
}, receiveValue: { object in
print("Retrieved object \(object)")
})
.store(in: &subscriptions)
This works, but Combine introduces some syntactical sugar to help get around that tryMap
block. The decode(type: decoder:)
operator takes in a decoder instead of a Data
object, instead expecting that data from upstream. You still need to extract out the data part of the tuple, so you still need to use map
. Replace the tryMap
block with the map
operator followed by the decode
operator.
.map(\.data)
.decode(type: Todo.self, decoder: JSONDecoder())