That is going to be a extremely quick, however hopefully very helpful tutorial about how I began to make the most of the Mix framework to slowly substitute my Promise library. 🤫
API & information construction
To start with we will want some type of API to attach, as ordinary I will use my favourite JSONPlaceholder service with the next information fashions:
enum HTTPError: LocalizedError {
case statusCode
case submit
}
struct Publish: 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 parts, and a easy error, as a result of hell yeah, we wish to present some error if one thing fails. ❌
The normal method
Doing an HTTP request in Swift is fairly simple, you should utilize the built-in shared URLSession with a easy information activity, and voilá there’s your response. In fact you would possibly wish to examine for legitimate standing code and if every part is okay, you may parse your response JSON through the use of the JSONDecoder object from Basis.
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let activity = URLSession.shared.dataTask(with: url) { information, 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 information = information else {
fatalError("Error: lacking response information")
}
do {
let decoder = JSONDecoder()
let posts = attempt decoder.decode([Post].self, from: information)
print(posts.map { $0.title })
}
catch {
print("Error: (error.localizedDescription)")
}
}
activity.resume()
Do not forget to renew your information activity or the request will not hearth in any respect. 🔥
Information duties and the Mix framework
Now as you may see the standard “block-based” method is good, however can we do perhaps one thing higher right here? You realize, like describing the entire thing as a sequence, like we used to do that with Guarantees? Starting from iOS13 with the assistance of the wonderful Mix framework you truly can go far past! 😃
My favourite a part of Mix is reminiscence administration & cancellation.
Information activity with Mix
So the most typical instance is normally the next one:
non-public var cancellable: AnyCancellable?
self.cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map { $0.information }
.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 to your Writer
- Then we create a model new information activity writer object
- Map the response, we solely care in regards to the information half (ignore errors)
- Decode the content material of the information utilizing a JSONDecoder
- If something goes unsuitable, simply go together with an empty array
- Erase the underlying complexity to a easy AnyPublisher
- Use sink to show some information in regards to the closing worth
- Non-obligatory: you may cancel your community request any time
Error dealing with
Let’s introduce some error dealing with, as a result of I do not like the thought 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.information
}
.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 unsuitable 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 may examine the result of your entire operation so you are able to do your individual error thingy there, like displaying an alert. 🚨
Assign outcome 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 through the use of 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.information }
.decode(sort: [Post].self, decoder: JSONDecoder())
.replaceError(with: [])
.eraseToAnyPublisher()
.assign(to: .posts, on: self)
}
}
Very simple, you can too 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 up to now. Now we have now Compose and this activity is simply ridiculously simple 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.information }
.decode(sort: [Post].self, decoder: JSONDecoder())
let publisher2 = URLSession.shared.dataTaskPublisher(for: url2)
.map { $0.information }
.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 may chain two HTTP calls along with just some traces 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.information }
.decode(sort: [Post].self, decoder: JSONDecoder())
.tryMap { posts in
guard let id = posts.first?.id else {
throw HTTPError.submit
}
return id
}
.flatMap { id in
return self.particulars(for: id)
}
.sink(receiveCompletion: { completion in
}) { submit in
print(submit.title)
}
}
func particulars(for id: Int) -> AnyPublisher<Publish, Error> {
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/(id)")!
return URLSession.shared.dataTaskPublisher(for: url)
.mapError { $0 as Error }
.map { $0.information }
.decode(sort: Publish.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 will probably do quite a bit, however it undoubtedly has some studying curve. Sadly you may solely use it in case you are concentrating on iOS13 or above (because of this you’ve one complete yr to be taught each single little bit of the framework) so assume twice earlier than adopting this new know-how.
You must also word that at the moment there isn’t a add and obtain activity writer, however you may make your very personal resolution till Apple formally releases one thing. Fingers crossed. 🤞
I actually love how Apple applied some ideas of reactive programming, I am unable to watch for Mix to reach as an open supply package deal with Linux help as effectively. ❤️