티스토리 뷰
Combine → Swift Concurrency(async/await)로 바꾸기 (feat. 느낀점)
Zedd0202 2022. 1. 25. 23:02
안녕하세요 :) Zedd입니다.
오늘은!!!! 주말을 맞아 그동안 미루고 미뤄왔던 Combine -> async/await로 바꾸기...ㅋㅋ..
아주 간단한 앱이어서 호다닥 바꿔볼 수 있을 것 같습니다.
참고 : 이 앱은 Deployment Target이 15.0입니다..
# 구조
API호출이 딱 하나 있는 아주 간단한 SwiftUI앱입니다.
[API.swift]
위와 같이 Combine을 사용해서 network request를 수행하고
[Service.swift]
service쪽에서 API에 있는 perfom 메소드를 수행합니다.
[ViewModel.swift]
그래서 ViewModel쪽에서
이렇게 service의 request를 호출하여 응답을 @Published로 mark된 변수에 넣어줍니다.
대충 진짜 뻔한 SwiftUI 앱인거 아시겠죠..!?!?
# Swift Concurrency
됐고...async 부터 달아본다..
[API.swift]
AS-IS
func perform<T: Decodable>(method: HTTPMethod, url: String?) -> AnyPublisher<HTTPResponse<T>, Error> {
TO-BE ✅
func perform<T: Decodable>(method: HTTPMethod, url: String?) async throws -> HTTPResponse<T>
1. AnyPublisher 삭제
2. 메소드에 async throws 추가
리턴타입이 바뀌었으니 return부분도 수정되어야되겠죠?
AS-IS
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()
TO-BE ✅
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)
dataTaskPublisher대신 data(from:) 이라는 async 메소드를 사용하도록 바꿨습니다.
당연히 await 과 함께 불려줘야겠죠!!
그냥 동기 메소드처럼 리턴해주면 끝...
⚠️ URLSession.shared.data는 iOS 15부터 사용가능 ㅋㅋ... **
[Service.swift]
service쪽에서 .perform(~~) 을 수행하던 곳이 있겠죠?
위 메소드도 수정이 필요할 것 같습니다.
역시나 일단 메소드 정의부에 async를 붙히고
AS-IS
func request() -> AnyPublisher<[SomeType], Error> {}
TO-BE ✅
func request() async throws -> [SomeType]
return역시 수정해줍시다.
AS-IS
return API.shared.perform(method: .get, url: component?.string)
.map(\.value)
.eraseToAnyPublisher()
TO-BE ✅
return try await API.shared.perform(method: .get, url: component?.string).value
끝!
[ViewModel.swift]
이제 viewModel쪽에서 service.request().sink~~ 하던 부분이 있을텐데요.
요기도 바뀌어야겠죠
Zedd : 응 일단 async 붙혀~~~
🥲 : 하...그럼 View에서 viewModel.requestSomething하던 곳들 어떡하지...에러나는데..
SomeView()
...
.onAppear(perform: { // 🚨 Invalid conversion from 'async' function of type '() async -> Void' to synchronous function type '() -> Void'
await self.viewModel.requestSomething()
})
(위 코드는 예제코드입니다.. iOS 15에서 task modifier가 나왔으니 쓸 수 있다면 task를 쓰시는게...)
이때 2가지 방법이 있습니다.
1. ViewModel > 메소드 > Task안에서 async메소드 호출하기
2. ViewModel > async 메소드로 바꾸고 호출하는 쪽이 Task안에서 호출하기
# Task? (feat. ViewModel > 메소드 > Task안에서 async메소드 호출하기)
Task가 뭔지 모르시는분들도 계실텐데요!
아주아주 간단하게 말해서..non-concurrent한 메소드 안에 concurrent한 환경을 만들어주는 역할을 하게 됩니다.
ViewModel.swift에 있는 requestSomething은 다음과 같이 non-concurrent 메소드인데요.
원래 async메소드를 호출하려면 requestSomething역시 concurrent 메소드가 되어야 합니다.
하지만 아래와같이
@Published var response: [SomeType] = []
func requestSomething() {
Task {
self.response = try await self.service.request()
}
}
Task가 non-concurrent한 메소드 안에 concurrent한 환경을 만들어주기 때문에 이렇게 호출할 수 있는 것이죠.
그리고 호출부에서도
SomeView()
...
.onAppear(perform: {
self.viewModel.requestSomething()
})
async메소드가 아니기 때문에 await 없이 그냥 호출해주면 됩니다.
1. ViewModel > 메소드 > Task안에서 async메소드 호출하기 ✅
2. ViewModel > async 메소드로 바꾸고 호출하는 쪽이 Task안에서 호출하기
1번은 방금 봤으니 패스... 2번은 어떤건지 감이 오시나요?
# ViewModel > async 메소드로 바꾸고 호출하는 쪽이 Task안에서 호출하기
@Published var response: [SomeType] = []
func requestSomething() async {
do {
self.response = try await self.service.request()
} catch {
// some code
}
}
ViewModel.swift 쪽 메소드를 async 메소드로 바꾸고,
requestSomething을 호출하는 호출부를 Task로 감싸주면 됩니다.
SomeView()
...
.onAppear(perform: {
Task {
await self.viewModel.requestSomething()
}
})
이렇게!
그러면 onAppear같이 non-concurrent한 메소드 안에서 concurrent 환경이 만들어지는것이죠.
⚠️ ---------- ⚠️
onAppear대신 iOS 15에서 나온 task modifier를 쓰면
.task {
await self.viewModel.requestSomething()
}
그냥 바로 이렇게 가능!
참고 : SwiftUI, iOS 15+ ) onAppear()대신 task()
⚠️ ---------- ⚠️
# 느낀점
1. Task만 있다면 Combine -> Swift Concurrency는 어렵지 않겠다..? 라는 생각이 들었습니다.
이게 Combine이 아니라 RxSwift여도요!
(근데 RxSwift도 Swift Concurrency지원하게 업데이트 됨)
2. URLSession.shard.data가 iOS 15부터 사용가능한건 좀 ㅠ...
여기가 async로 깔끔하게 안풀리면 끔찍한 혼종이 발생할 것 같다는 생각이 들었습니다.
아직 해보진 않음...
앱이 워낙 간단하다보니 딱히 트러블슈팅도 없었고....그래서 딱히 느낀점으로 쓸만한게 없네요.
이렇게 아주 간단하게 Combine -> Swift Concurrency를 사용하도록 바꿔봤는데
저도 잘 몰라서 틀린 부분이 있을지도 모르겠습니다 ㅎ (특히 Task...)
Task는 공부를 제대로 한번 해봐야겠어요. iOS 13부터 사용가능하니..많이 쓰게 될 듯한ㅎㅎ
틀린 부분이나 개선될 수 있는 부분들을 발견하셨다면 댓글 남겨주세요!
'Swift > Concurrency' 카테고리의 다른 글
AsyncStream / AsyncThrowingStream (feat. RxSwift + Concurrency) (0) | 2022.02.07 |
---|---|
AsyncSequence (0) | 2022.02.06 |
Actor (4) - Sendable (3) | 2021.10.11 |
Swift ) Actor (3) - isolated parameter / nonisolated keyword (1) | 2021.08.29 |
Swift ) Actor (2) - Actor isolation (7) | 2021.08.23 |
- swift 공부
- SwiftUI
- np-complete
- Git
- Swift
- github
- actor
- 피아노
- Xcode
- WidgetKit
- Combine
- swift3
- swift tutorial
- IOS
- fastlane
- 제이슨 파싱
- swift sort
- 회고
- swift delegate
- FLUTTER
- 스위프트 문법
- 스위프트
- WKWebView
- iOS delegate
- UIBezierPath
- Accessibility
- np-hard
- WWDC
- ios 13
- swift array
- Total
- Today
- Yesterday