Replacing combine to async/await
With the advent of async/await in Swift 5.5, many things have been replaced. Typical examples are Closure and Combine, which helped asynchronous programming.
Async/await
If you look at the Apple's official document, the following tips are written. It is said to eliminate the need for closures and combines with async-await.
// You don’t need closure-based asychronicity patterns if you’re using the async-await features in Swift 5.5 and later.
// Instead, your code can await an asynchronous call, and then execute the code that would have been in the closure.
// This eliminates the need for both conventional completion handlers and Combine futures.
// For more information, see Concurrency in The Swift Programming Language.
Example
There are three example files API.swift, Service.swift, and ViewModel.swift implemented in Combine as follows.
// API.swift
class API {
func perform<T: Decodable>(method: HTTPMethod, url: String?) -> AnyPublisher<HTTPResponse<T>, Error> {
...
return URLSession.shared.dataTaskPublisher(for: request)
.tryMap { result -> HTTPResponse<T> in
let value = try self.decoder.decode(T.self, from: result.data)
return HTTPResponse(value: value, response: result.response)
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
// Service.swift
class Service: ServiceType {
...
func request() -> AnyPublisher<[SomeType], Error> {
...
return API.shared.perform(method: .get, url: component?.string)
.map(\.value)
.eraseToAnyPublisher()
}
}
// ViewModel.swift
func requestSomething() {
self.service.request()
.sink { _ in
...
} receiveValue: { response in
self.response = response
}.store(in: &self.cancellable)
}
API.swift
First, I removed AnyPublisher from the function declaration and added async throws.
// Combine
func perform<T: Decodable>(method: HTTPMethod, url: String?) -> AnyPublisher<HTTPResponse<T>, Error>
// Async/await
func perform<T: Decodable>(method: HTTPMethod, url: String?) async throws -> HTTPResponse<T>
Then change the return type accordingly. Use the async method data(from:) instead of the dataTaskPublisher.
// Combine
return URLSession.shared.dataTaskPublisher(for: request)
.tryMap { result -> HTTPResponse<T> in
let value = try self.decoder.decode(T.self, from: result.data)
return HTTPResponse(value: value, response: result.response)
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
// Async/await
let (data, urlResponse) = try await URLSession.shared.data(from: url)
let response = try decoder.decode(T.self, from: data)
return HTTPResponse(value: response, response: urlResponse)
Service.swift
This is the change in Service.swift. Similarly, I removed AnyPublisher and added async throws.
// Combine
func request() -> AnyPublisher<[SomeType], Error> {}
// Async/await
func request() async throws -> [SomeType]
As expected, I will modify the return accordingly.
// Combine
return API.shared.perform(method: .get, url: component?.string)
.map(\.value)
.eraseToAnyPublisher()
// Async/await
return try await API.shared.perform(method: .get, url: component?.string).value
ViewModel.swift
For ViewModel file, I received the value in Task instead of using sink.
// Combine
func requestSomething() {
self.service.request()
.sink { _ in
...
} receiveValue: { response in
self.response = response
}.store(in: &self.cancellable)
}
// Async/await
func requestSomething() {
Task {
self.response = try await self.service.request()
}
}
You can call this as the following way.
View()
.onAppear {
self.viewModel.requestSomething()
}
Result
These are the results of three files implemented using async/await instead of Combine. Personally, I think it's much more intuitive and simple.
// API.swift
class API {
func perform<T: Decodable>(method: HTTPMethod, url: String?) async throws -> HTTPResponse<T> {
...
let (data, urlResponse) = try await URLSession.shared.data(from: url)
let response = try decoder.decode(T.self, from: data)
return HTTPResponse(value: response, response: urlResponse)
}
}
// Service.swift
class Service: ServiceType {
...
func request() async throws -> [SomeType]
...
return try await API.shared.perform(method: .get, url: component?.string).value
}
}
// ViewModel.swift
func requestSomething() {
Task {
self.response = try await self.service.request()
}
}