본문 바로가기

Combine 이벤트 체인이 왜 필요할까?

기술적인 이야기/애플 플랫폼 개발 2020. 1. 23.
반응형

앞서 작성한 글에서 Combine Framework의 기본 개념인 퍼블리셔(Publisher)와 서브스크라이버(Subscriber), 그리고 서브젝트(Subject)에 대해 간략히 살펴봤습니다. 이번 글에서는 왜 이런 것을 이용해 프로그래밍을 하는지에 대한 이유를 정리해보고자 합니다. 사실 잘 설명할 수 있을지는 의문이긴 합니다만, 개인적으로도 글로 정리하는 것이 큰 공부가 되기에 억지로(?) 글을 써 봅니다.

전통적인 예제

그냥 다짜고짜 예제부터 시작해 봅시다.

var value = 1
if value % 2 == 0 {
    print("\(value) is even!")
}

이 코드의 의도는 value의 값이 짝수인지 판단하는 것입니다. 짝수이면 해당 값을 콘솔에 찍지요. 실행시키면 당연하게도 1은 짝수가 아니니 아무것도 표시되지 않습니다.

뭔가 문제될 것은 없지요? 그런데 여기에 약간의 소금(?)을 쳐 봅시다.

var value = 1
if value % 2 == 0 {
    print("\(value) is even!")
}

value = 2  // what?! 뭐 어쩌라는 걸까?

마지막에 한 줄을 추가했습니다. 네. 사실 아무런 의미가 없는 코드라는 거 다들 알고 있습니다. value의 값을 나중에 2로 바뀌어 봤자 그 앞의 if문은 실행되지 않아요. 왜냐하면 스위프트의 코드는 기본적으로 위에서 아래로 순차적으로 실행되기 때문입니다.

그런데 이게 당연하지 않다고 할 수 있을까요? 만약 value = 2라는 구분이 실행되면 갑자기 "2 is even!"이라는 문자열이 찍혀버리는 것은 이상한가요?

다른 패러다임을 이해해 봅시다. 컴바인의 방식으로 말이죠.

체인을 만들어 보자

자 그래서 똑같은 역할을 하는 코드를 다르게 작성해 보겠습니다.

let subject = PassthroughSubject<Int, Never>()
let subscription = subject
    .filter { $0 % 2 == 0}
    .sink(receiveValue: { value in
        print("\(value) is even!")
    })

subject.send(1)

위 예제는 앞서 살펴본 첫 예제를 컴바인 식으로 구현해 본 예제입니다. 물론 실행시키면 결과도 똑같이 아무런 것도 표시되지 않습니다. 1은 짝수가 아니니깐요.

그리고 value = 2라는 이상한 짓을 했던 것과 비슷하게 아래 한 줄의 코드를 제일 아래에 추가해 볼까요?

subject.send(2)

하지만 이번에는 다릅니다. 앞에서 value = 2라는 코드가 무의미하다고 했던 것과는 다르게, 컴바인으로 구현된 서브스크립션 체인은 데이터가 공급되면 그 즉시 동작합니다. 따라서 콘솔에는 "2 is even!"이라는 메시지가 출력됩니다.

컴바인은 이벤트 체인 혹은 데이터 처리 체인을 구현하여 데이터의 입력을 이벤트의 시작으로 받아 그 출력까지의 각 단계를 (대부분) 비동기로 처리하는 것이 목적인 프레임워크입니다. 이 점을 이해한다면 아마도 컴바인 프레임워크의 가장 중요한 것을 이해한 것이 아닐까 생각됩니다.

물론 굳이 이렇게 하지 않고 더 간단히 함수 하나 만들면 되지 않냐고 할 수 있는데 맞습니다. 그냥 제가 예제를 작성할 수 있는 능력이 여기까지 인 게 문제일 뿐입니다. 😭

코드의 부연 설명

PassthroughSubject

PassthroughSubject이전 글에서 설명한 서브젝트(Subject)라는 개념으로 특수한 퍼블리셔(Publisher)입니다. 이 PassthroughSubject.send() 메서드를 통해 체인에 이벤트를 밀어 넣는(Push) 데 특화된 서브젝트입니다.

모든 퍼블리셔가 그렇지만 이 서브젝트도 데이터의 타입과 에러 타입을 반드시 정의해야 합니다. 위 예제에서 데이터 타입은 Int이고 에러는 취급하지 않기 때문에 Never로 명시했습니다.

서브젝트를 다르게 설명하는 말로 Mutable Publisher라는 말이 있습니다. 그리고 이와는 다르게 Just(10)과 같이 Immutable Publisher도 있지요.

.filter()

.filter()는 오퍼레이터(Operator)입니다. 오퍼레이터는 퍼블리셔를 통해 전달된 이벤트를 가공하거나 특수하게 처리하는 역할을 합니다. 특히 .filter()의 경우 클로저에서 리턴하는 결과가 false일 경우 해당 이벤트를 체인에서 퇴출(?)시켜 버립니다.

오퍼레이터는 특별해 보입니다만 사실 그 존재 자체로는 퍼블리셔와 별로 다를 바는 없습니다. 다른 체인에 붙어서 다음 체인에 이벤트를 던져줄 수 있기 때문이지요. 그저 체인의 중간에 끼어 있다는 점이 일반 퍼블리셔(?)와 다를 뿐이지요.

.sink()

sink()는 이미 이전 글에서 간단한 서브스크라이버(Subscriber)로써 설명했었던 바로 그것입니다. 그 외에 컨슈머(Consumer)라고도 불립니다. 위 예제에서는 이벤트 체인의 끝에서 결과를 출력하고 끝맺는 용도로 사용됩니다.

마무리

사실 이 글의 예제는 좋은 예제가 아닙니다. 저도 공부를 하면서 글을 쓰는 입장이기 때문입니다.

하지만 약간 생각을 확장해보면 좋은 예가 될 수도 있습니다. 서브젝트의 입력이 데이터가 아니라 사용자의 터치 액션이라며 어떻게 될지 말이죠. sink 대신 assign을 쓰면 UI를 손쉽게 업데이트할 수도 있고 모델의 데이터를 바꿀 수도 있습니다.

네... 이해가 안 되어도 이해합니다. 저도 아직 초보인걸요. 😭

관련 글

728x90
반응형

댓글