앞서 작성한 글에서 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를 손쉽게 업데이트할 수도 있고 모델의 데이터를 바꿀 수도 있습니다.
네... 이해가 안 되어도 이해합니다. 저도 아직 초보인걸요. 😭
관련 글
'기술적인 이야기 > 애플 플랫폼 개발' 카테고리의 다른 글
Combine과 SwiftUI (1) | 2020.01.28 |
---|---|
약간은 더 현실적인 Combine 예제들 (0) | 2020.01.27 |
Combine Framework는 어떤 녀석일까요? (0) | 2020.01.15 |
[Swift 5.1] Identifiable (SE-0261) (0) | 2019.11.12 |
[Swift 5.1] 리턴 생략 가능 (SE-0255) (0) | 2019.11.05 |
댓글