본문 바로가기

iOS공통

[Swift] Combine 4편: Combine을 사용한 비동기 코드처리

 

Hello guys it's junios again ✋✋✋

 

 

Today I want to write this post with English 😎

 

 

Last time, I introduced Subscriber Protocol and how to use it with pub and Demand mechanism.

 

 

This time we gonna find out how can we use Asychronous work with Combine.

 

 

Before start, there are typical ways to develope this work.

 

 

1. Handing completion handler to some worker and expect it to be called when work wil be finished.

 

2. Implementing closure property(not method) and expect it to be called when some work will be finished.

[ex - delegate design pattern]

 

 

주접그만싸고 한국어로 포스팅을 이어가겠습니다.😆

 

 

Combine프레임워크를 사용하면 두가지 방식을 동일한 방식으로 구현할 수 있습니다. 

 

 

이렇게 방법을 통일함으로써 코드의 유지보수성을 높이고 가독성역시 높일 수 있습니다.

 

그리고 Combine의 강력한 장점을 사용할 수 있고요!

 

 

이쯤되서 한번더 상기하겠습니다. Combine의 장점이 무었입니까?🤔

 

 

Combine의 장점은 다양한 Operator를 사용하여 유동적인 pub를 만들 수 있는 것입니다.

 

 

자, 여기까지 내용을 인지한채로 기존의 방식을 변경해 보도록 하겠습니다.

 

 

Completion Handler 방식 → Combine

 

Combine에는 Future라는 빌트인 타입이 존재합니다. 

 

 

Future은 Combine에 구현되어있는 Publisher채택 타입입니다.

 

 

여느 pub과의 차이점은 한번의 publish이후 sub와의 커넥션을 종료합니다.

 

 

Future타입은 인스턴스 생성시 연과타입인 Output, Failure를 지정해야 하며 매개변수로 클로저를 전달합니다.

 

 

let pub = Future<String, Never>() { promise in
    promise(.success("Hello world"))
}

 

 

Future타입은 promise라는 클로저를 매개변수로 전달받는 클로저를 전달 받습니다.

 

 

말이좀 어려운데요. 위 코드를 보시면 매개변수로 전달받은 promise클로저를 호출하는 것을 확인할 수 있습니다.

 

 

해당 클로저는 Result<Ouput, Failure>타입을 매개변수로 전달받습니다.

 

 

이렇게 함으로써 completion클로저를 전달하는 비동기 처리방식을 대체할 수 있습니다.

 

 

아래코드는 Combine에 강점을 더한 비동기 처리 방식입니다.

 

 

import Combine

let pub = Future<String, Never>() { promise in
    promise(.success("Hello"))
}
.makeConnectable()

			✋✋✋✋✋
let sub1 = pub.map { $0 + ", world!" }.sink { print($0) }
			✋✋✋✋✋
let sub2 = pub.map { $0 + ", junyeong!" }.sink { print($0) }

pub.connect()


///Hello, junyeong!
///Hello, world!

 

앞서 Future인스턴스는 한번의 publish후 sub와의 연결을 종료한다고 했습니다.

 

 

위 코드의 결과를 보면 한번의 publish로 두개의 sub가 모두 이벤트를 수령함을 확인할 수 있습니다.

 

 

promise클로저에는 Result타입의 인스턴스를 전달하지만 결국 sub가 전달받는 element는 Result내부 값입니다.

 

 

Combine의 Operator장점을 살리면서 completion클로저를 전달한 비동기 처리 방식구현이 완료되었습니다.

 

 

promise로 Result.failure케이스를 전달하여 오류가 발생했음을 sub에 전달할 수 있습니다.

 

 

let pub = Future<String, MyError>() { promise in
    DispatchQueue.global().asyncAfter(deadline: .now()+2) {
        //비동기 작업내 에러 발생
        promise(.failure(.someError))
    }
}
pub.sink { result in
    switch result {
    case .finished:
        print("finished")
    case .failure(let error):
        print(error)
    }
} receiveValue: { print($0) }

///someError

 

해당 변환작업의 장점을 마지막으로 정리해 보면

 

 

한번의 publish를 다양한 sub가 각기다른 Operator로 결과처리가 가능하다 그리고 코드도 간결하다! 입니다.

 

 

 

다음으로는 프로퍼티에 작업(클로저)를 저장하고 특정 시점에 해당 클로저를 호출하는 비동기 처리 방식을 바꿔보겠습니다.

 

 

 

프로퍼티 클로저 → Combine

 

해당기능을 구현하기 위해 Combine에 구현되어 있는 Subject를 사용할 것입니다.

 

 

Subject에 대한 내용은 전의 포스팅에서 다루었습니다.

 

 

기존에 구현 방식은 프로퍼티에 클로저를 저장하고 해당 프로퍼티를 호출하는 방식으로 비동기 작업의 종료를 알렸습니다.

 

 

이 방식을 프로퍼티에 Subject인스턴스를 저장하는 방식으로 변경하겠습니다.

 

 

struct MyScruct {
    var pub = CurrentValueSubject<String, Never>("initial Str")
}

 

 

이렇게 구현한 후 아래코드와 같이 특정 비동기 작업 종료후 send매서드를 사용합니다.

 

 

var instance = MyScruct()

instance.pub.sink { print($0) }

DispatchQueue.global().asyncAfter(deadline: .now()+2) {
    instance.pub.send("Hello world")
}


///initial Str
///Hello world

 

이렇게 변경함으로써 Combine의 장점인 Operator를 적용하는 것도 당연히 가능하겠지요? 🤗

 

 

자, 여기까지 기존의 비동기 처리방식을 Combine을 사용하는 방식으로 변경해 보았습니다.

 

 

 

마무리

 

 

앱개발시 API를 보통 비동기로 요청합니다, 이 과정에서 상황마다 각기다른 클로저를 전달하는 것은 상당히 까다로운 일입니다.

 

 

이것을 Combine을 사용하여 간편화할 수 있습니다. 

 

 

가독성 높고 유지보수가 높은 코드를 작성할 수 있는 것이지요! 😎

 

 

다음 편에서는 Foundation Timer이벤트를 Combine으로 대체해 보겠습니다.

 

 

 

다음편에서 봐요! ✋✋✋

 

 

 

 

 

Using Combine for Your App’s Asynchronous Code | Apple Developer Documentation

Apply common patterns to migrate your closure-based, event-handling code.

developer.apple.com