안녕하세요! 오늘은 Combine을 사용해서 비동기 코드를 변경하는 방법에 대해 설명하는 문서를 정리해 보겠습니다.
우리는 실제로 개발을 하다 보면 비동기 코드를 사용하는 경우가 많습니다. 대표적으로 Completion Handler, Closure property을 주로 사용합니다.
Combine을 사용하면 위에서 얘기한 2가지 방법과 동등한 기능을 제공할 수 있고 코드의 가독성을 향상시킬 수 있습니다.
Swift 5.5 이상에서는 async - await을 사용하여 closure 기반의 비동기 코드를 대체할 수 있습니다. async - awiat 코드를 활용하면 completion handler와 Combine도 필요 없습니다.
Completion Handler는 비동기 코드가 언제 종료될지 예측을 할 수 없기 때문에 함수가 종료되고 호출되는 함수입니다. 간단한 예를 먼저 보겠습니다.
func performAsyncAction(completionHandler: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline:.now() + 2) {
completionHandler()
}
}
위 함수는 2초 뒤에 completion handler가 호출됩니다. 그러면 이 코드를 Combine을 사용해 수정할 수 있을까요?
Future는 작업을 수행한 다음 단일 element를 비동기식으로 게시하는 역할을 합니다. 또한 작업이 정상적으로 성공하면 completion handler 대신 Future.Promise를 호출합니다. Promise를 통해 성공 또는 실패를 제공하는 Result 타입을 전달합니다.
func performAsyncActionAsFuture() -> Future <Void, Never> {
return Future() { promise in
DispatchQueue.main.asyncAfter(deadline:.now() + 2) {
promise(Result.success(()))
}
}
}
Future를 통해 제공되는 값을 받기 위해서는 Subscriber를 이용해야 합니다. 예시에서는 가장 간단하게 Subsciber를 생성할 수 있는 sink(receiveValue:)
함수를 사용하고 있습니다.
cancellable = performAsyncActionAsFuture()
.sink() { _ in print("Future succeeded.") }
Future의 Output을 설정해 값을 전달할 수 있습니다.
func performAsyncActionAsFutureWithParameter() -> Future <Int, Never> {
return Future() { promise in
DispatchQueue.main.asyncAfter(deadline:.now() + 2) {
let rn = Int.random(in: 1...10)
promise(Result.success(rn))
}
}
}
cancellable = performAsyncActionAsFutureWithParameter()
.sink() { rn in print("Got random number \(rn).") }
위 코드를 보면 Future가 Int 값을 생성하고 있습니다. 이러한 값을 promise를 통해 전달하고 Subscriber를 통해 값을 받을 수 있습니다.
특정 이벤트가 발생할 때 반복적으로 호출되는 closure가 있습니다. 저 같은 경우에는 관찰하고 있는 데이터가 변하면 클로저를 호출해 UI를 업데이트하는데 많이 사용합니다.
Subject를 사용하면 이러한 케이스를 대체할 수 있습니다. 필요할 때 send()
함수를 호출하여 새 element를 임시로 제공할 수 있습니다. 그러기 위해서는 PassthroughSubect 또는 CurrentValueSubject를 사용한 다음 AnyPublisher를 생성하면 됩니다.
private lazy var myDoSomethingSubject = PassthroughSubject<Void, Never>()
lazy var doSomethingSubject = myDoSomethingSubject.eraseToAnyPublisher()
cancellable = vc.doSomethingSubject
.sink() { print("Did something with Combine.") }
Combine을 사용했을 때 장점이 subject가 send(complete:)
를 호출하여 subscriber에게 더 이상 이벤트가 발생하지 않거나 오류가 발생했음을 알릴 수 있다는 것입니다.
Swift 5.5 이후에서는 async - await 동시성을 사용하는 경우 Subject 대신 AsyncStream을 사용해 새로운 element를 비동기적으로 생성할 수 있습니다.
오늘은 기존에 사용하던 비동기 코드에 Combine을 적용하는 방법에 대해서 알아봤습니다. 그렇다면 이제부터 제 프로젝트를 직접 수정하면서 느낀 점이나 아쉬운 점에 대해 정리해 보도록 하겠습니다.