- Swift 5.5에
async/await
가 등장하면서, 많은 것을 대체하게 되었는데요. - 그 중 대표적인 것이 비동기 프로그래밍을 도와주던 Closure와 Combine입니다.
Async/await
- 공식 문서를 보면, 다음과 같은 팁이 적혀있습니다.
- 대략,
async/await
가 completion
클로저와 Combine의 필요성을 없앤다고 합니다.
Example
- 다음과 같이 Combine으로 구현 된 3가지 예시 파일 API, Service, ViewModel이 있습니다.
- 각각의 함수 선언부와 리턴부만 표기해보겠습니다.
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()
}
}
class Service: ServiceType {
...
func request() -> AnyPublisher<[SomeType], Error> {
...
return API.shared.perform(method: .get, url: component?.string)
.map(\.value)
.eraseToAnyPublisher()
}
}
func requestSomething() {
self.service.request()
.sink { _ in
...
} receiveValue: { response in
self.response = response
}.store(in: &self.cancellable)
}
API.swift
- API.swift 변경 점 입니다.
- 우선 함수 선언부에서 AnyPublisher를 제거하고 async throws를 추가했습니다.
func perform<T: Decodable>(method: HTTPMethod, url: String?) -> AnyPublisher<HTTPResponse<T>, Error>
func perform<T: Decodable>(method: HTTPMethod, url: String?) async throws -> HTTPResponse<T>
- 그에 맞게 return 타입도 변경해줍니다.
- dataTaskPublisher 대신 async 메소드인 data(from:)을 사용합니다.
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()
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
- Service.swift 변경 점 입니다.
- 똑같이 AnyPublisher를 제거하고 async throws를 추가했습니다.
func request() -> AnyPublisher<[SomeType], Error> {}
func request() async throws -> [SomeType]
- 역시나 그에 맞게, return을 수정해줍니다.
return API.shared.perform(method: .get, url: component?.string)
.map(\.value)
.eraseToAnyPublisher()
return try await API.shared.perform(method: .get, url: component?.string).value
ViewModel.swift
- ViewModel.swift 변경 점 입니다.
- sink로 값을 받아오던 것과는 다르게 Task로 값을 받아옵니다.
func requestSomething() {
self.service.request()
.sink { _ in
...
} receiveValue: { response in
self.response = response
}.store(in: &self.cancellable)
}
func requestSomething() {
Task {
self.response = try await self.service.request()
}
}
View()
.onAppear {
self.viewModel.requestSomething()
}
결과물
- Combine 대신
async/await
를 사용해서 구현해본 3가지 파일의 결과물입니다. - 개인적으로는 훨씬 직관적이고 간단해졌다고 생각합니다.
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)
}
}
class Service: ServiceType {
...
func request() async throws -> [SomeType]
...
return try await API.shared.perform(method: .get, url: component?.string).value
}
}
func requestSomething() {
Task {
self.response = try await self.service.request()
}
}
참고