Learn to make HTTP requests and parse the response utilizing the model new Mix framework with basis networking.
iOS
That is going to be a very quick, however hopefully very helpful tutorial about how I began to make the most of the Combine framework to slowly substitute my Promise library. 🤫
API & knowledge construction
To start with we’ll want some form of API to attach, as ordinary I’ll use my favourite JSONPlaceholder service with the next knowledge fashions:
enum HTTPError: LocalizedError
case statusCode
case put up
struct Submit: Codable
let id: Int
let title: String
let physique: String
let userId: Int
struct Todo: Codable
let id: Int
let title: String
let accomplished: Bool
let userId: Int
Nothing particular to this point, just a few fundamental Codable
components, and a easy error, as a result of hell yeah, we need to present some error if one thing fails. ❌
The standard means
Doing an HTTP request in Swift is fairly straightforward, you should use the built-in shared URLSession with a easy knowledge activity, and voilá there’s your response. In fact you would possibly need to examine for legitimate standing code and if every little thing is okay, you possibly can parse your response JSON by utilizing the JSONDecoder object from Basis.
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let activity = URLSession.shared.dataTask(with: url) knowledge, response, error in
if let error = error
fatalError("Error: (error.localizedDescription)")
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else
fatalError("Error: invalid HTTP response code")
guard let knowledge = knowledge else
fatalError("Error: lacking response knowledge")
do
let decoder = JSONDecoder()
let posts = attempt decoder.decode([Post].self, from: knowledge)
print(posts.map $0.title )
catch
print("Error: (error.localizedDescription)")
activity.resume()
Do not forget to renew your knowledge activity or the request will not fireplace in any respect. 🔥
Information duties and the Mix framework
Now as you possibly can see the standard “block-based” strategy is sweet, however can we do perhaps one thing higher right here? You understand, like describing the entire thing as a series, like we used to do that with Guarantees? Starting from iOS13 with the assistance of the wonderful Combine framework you really can go far past! 😃
My favourite a part of Mix is reminiscence administration & cancellation.
Information activity with Mix
So the most typical instance is often the next one:
non-public var cancellable: AnyCancellable?
self.cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map $0.knowledge
.decode(sort: [Post].self, decoder: JSONDecoder())
.replaceError(with: [])
.eraseToAnyPublisher()
.sink(receiveValue: posts in
print(posts.depend)
)
self.cancellable?.cancel()
I like how the code “explains itself”:
- First we make a cancellable storage on your Writer
- Then we create a model new knowledge activity writer object
- Map the response, we solely care in regards to the knowledge half (ignore errors)
- Decode the content material of the information utilizing a JSONDecoder
- If something goes flawed, simply go along with an empty array
- Erase the underlying complexity to a easy AnyPublisher
- Use sink to show some information in regards to the last worth
- Non-obligatory: you possibly can cancel your community request any time
Error dealing with
Let’s introduce some error handling, as a result of I do not like the concept of hiding errors. It is so significantly better to current an alert with the precise error message, is not it? 🤔
enum HTTPError: LocalizedError
case statusCode
self.cancellable = URLSession.shared.dataTaskPublisher(for: url)
.tryMap output in
guard let response = output.response as? HTTPURLResponse, response.statusCode == 200 else
throw HTTPError.statusCode
return output.knowledge
.decode(sort: [Post].self, decoder: JSONDecoder())
.eraseToAnyPublisher()
.sink(receiveCompletion: completion in
change completion
case .completed:
break
case .failure(let error):
fatalError(error.localizedDescription)
, receiveValue: posts in
print(posts.depend)
)
In a nutshell, this time we examine the response code and if one thing goes flawed we throw an error. Now as a result of the writer may end up in an error state, sink has one other variant, the place you possibly can examine the end result of the complete operation so you are able to do your personal error thingy there, like displaying an alert. 🚨
Assign consequence to property
One other widespread sample is to retailer the response in an inside variable someplace within the view controller. You may merely do that by utilizing the assign perform.
class ViewController: UIViewController
non-public var cancellable: AnyCancellable?
non-public var posts: [Post] = []
didSet
print("posts --> (self.posts.depend)")
override func viewDidLoad()
tremendous.viewDidLoad()
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
self.cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map $0.knowledge
.decode(sort: [Post].self, decoder: JSONDecoder())
.replaceError(with: [])
.eraseToAnyPublisher()
.assign(to: .posts, on: self)
Very straightforward, it’s also possible to use the didSet
property observer to get notified about modifications.
Group a number of requests
Sending a number of requests was a painful course of previously. Now now we have Compose and this activity is simply ridiculously straightforward with Publishers.Zip
. You may actually mix a number of requests togeter and wait till each of them are completed. 🤐
let url1 = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let url2 = URL(string: "https://jsonplaceholder.typicode.com/todos")!
let publisher1 = URLSession.shared.dataTaskPublisher(for: url1)
.map $0.knowledge
.decode(sort: [Post].self, decoder: JSONDecoder())
let publisher2 = URLSession.shared.dataTaskPublisher(for: url2)
.map $0.knowledge
.decode(sort: [Todo].self, decoder: JSONDecoder())
self.cancellable = Publishers.Zip(publisher1, publisher2)
.eraseToAnyPublisher()
.catch _ in
Simply(([], []))
.sink(receiveValue: posts, todos in
print(posts.depend)
print(todos.depend)
)
Similar sample as earlier than, we’re simply zipping collectively two publishers.
Request dependency
Generally you must load a useful resource from a given URL, after which use one other one to increase the article with one thing else. I am speaking about request dependency, which was fairly problematic with out Mix, however now you possibly can chain two HTTP calls along with only a few strains of Swift code. Let me present you:
override func viewDidLoad()
tremendous.viewDidLoad()
let url1 = URL(string: "https://jsonplaceholder.typicode.com/posts")!
self.cancellable = URLSession.shared.dataTaskPublisher(for: url1)
.map $0.knowledge
.decode(sort: [Post].self, decoder: JSONDecoder())
.tryMap posts in
guard let id = posts.first?.id else
throw HTTPError.put up
return id
.flatMap id in
return self.particulars(for: id)
.sink(receiveCompletion: completion in
) put up in
print(put up.title)
func particulars(for id: Int) -> AnyPublisher<Submit, Error>
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/(id)")!
return URLSession.shared.dataTaskPublisher(for: url)
.mapError $0 as Error
.map $0.knowledge
.decode(sort: Submit.self, decoder: JSONDecoder())
.eraseToAnyPublisher()
The trick right here is that you may flatMap
a writer into one other.
Conclusion
Mix is an incredible framework, it might probably do lots, but it surely undoubtedly has some studying curve. Sadly you possibly can solely use it in case you are focusing on iOS13 or above (which means you’ve gotten one complete 12 months to be taught each single little bit of the framework) so suppose twice earlier than adopting this new know-how.
You also needs to observe that at present there isn’t a upload and download task publisher, however you can also make your very personal answer till Apple formally releases one thing. Fingers crossed. 🤞
I actually love how Apple carried out some ideas of reactive programming, I can not look forward to Mix to reach as an open supply bundle with linux assist as effectively. ❤️