[Combine] sink & assign
이전 포스팅에서는 직접 custom subscriber을 구현해보았습니다!
하지만 은 이렇게 Subscriber
을 상속받아 직접 구현하는 것을 권장하지 않아요! 저걸 일일히 구현하는 것도 물론 귀찮은 일이고요! 그럼 어떻게하란 말이냐!!
Combine
에서는 Sink
, Assign
등의 기본 Subscriber
를 제공하고 있고 Publisher
protocol에 정의된 sink
나 assign
함수를 이용해서 각각의 subscription
을 간단히 구현할 수 있답니다🎉 오늘은 이 함수들에 대해 알아보아요🚀
만약
subscriber
나subscribe
에 대한 개념이 잘 안잡힌 상태에서 바로 이 글을 보시면 이해가 잘 안될 수 있어요! 왜냐면 제가 본 책에서 바로 예제에sink
부터 때려버리길래 ㅎ..? 뭐라고요..? 했던 경험이 있거든요ㅠ 그래서 저는 기본 개념들부터 정리하고sink
와assign
은 뒤로 빼서 작성합니다✍🏻 만약 읽다가 이해가 안되시면 이전 글부터 읽고 오시는걸 추천드려요
그럼 시작!
sink(receiveCompletion:receiveValue:)
sink(receiveCompletion:receiveValue:)
는 closure
에서 새로운 값이나 종료 이벤트에 대해 처리합니다.
예시를 봅시다.
example(of: "sink") {
// 1. Publisher의 한 종류인 Just를 생성합니다.
let just = Just("Hello world!")
// 2. .sink를 통해 publisher에 대한 subscription을 작성합니다.
_ = just
.sink(
receiveCompletion: {
print("Received completion", $0)
},
receiveValue: {
print("Received value", $0)
})
}
자 저번 시간에 다룬 publisher
를 구독하는 방법 기억나시나요?? publisher
와 subscriber
연결하는 subscription
만드려고 아래 코드를 사용했어요.
publisher.subscribe(subscriber)
아니 근데 파라미터로 들어가는 subscriber
을 먼저 만들어야하네..? Subscriber
protocol 받아서 Input
, Failure
타입 지정해야하지.. subscription
받을 때.. value
받을 때.. completion
받을 때 등등 Subscriber
protocol 로 정의되어 있는 함수 일일히 안에서 구현해야하지.. 아주 귀찮은 일이 많았단 말이죠?
하지만 sink
를 보십쇼. 그런거 다 필요 없고 value
나 completion
의 새로운 값만 편하게 받아서 처리할 수 있습니다!
——— Example of: sink ———
Received value Hello world!
Received completion finished
이렇게 편하게 사용할 수 있는 이유는 sink
를 사용할 때 그 귀찮은 protocol들을 모두 구현한 자체 subscriber
가 같이 제공되고 연결되기 때문이에요!!!! 거기다가 subscriber
타입은 Sink
!! 편-안 그 자체.
Sink
는 무제한 개수의 값을 요청한다는데..! 어라? 여기서 Subscriber
protocol 에 정의된 receive(subscription:)
함수가 기억나시나요?💡
receive(subscription:)
에서는protocol Subscription
에 정의된request(_:)
을 호출하여 값을 최대로 얼만큼 받을 것인지 설정합니다.
따라서 Sink
의 receive(subscription:)
구현부에는 이런 코드가 들어가있을거라는걸 예측할 수 있을거예요!
subscription.request(.unlimited)
이렇게 sink(receiveCompletion:receiveValue:)
를 사용하는것 만으로 우리는 publisher
와 subscriber
(Sink
타입)을 연결시키고 값을 받아 처리할 수 있습니다.
Combine
프레임워크는 오픈 소스가 아니라 정확히는 더 확인할 수 없지만 사람들이 OpenCombine 이라는 이름으로 오픈소스로 컴바인을 재현하고 있으니 궁금하신 분들은 확인해도 좋을것 같아요 (역시 세상에 천재들은 많다🤭)
assign(to:on:)
assign(to:on:)
는 새로운 값을keyPath
에 따라 주어진 인스턴스의 property
에 할당합니다.
example(of: "assign") {
// 1. didSet을 통해 value 값이 바뀌면 새 값을 print합니다.
class SomeObject {
var value: String = "" {
didSet {
print(value)
}
}
}
// 2. 위에서 만든 class의 instance를 선언합니다.
let object = SomeObject()
// 3. String 배열로 이뤄진 publisher를 생성합니다.
let publisher = ["Hello", "world!"].publisher
// 4. publisher를 구독하면서 새롭게 받은 값을 object의 value에 할당합니다.
_ = publisher
.assign(to: \.value, on: object)
}
앞에 sink
를 설명할때 기존에 publisher
를 구독하려면 얼마나 귀찮은 일들을 처리해야하는지 설명드렸어요! sink
가 그런 과정들 다 때려치우고 간단히 완료와 값에 대한 이벤트를 처리할 수 있게 해준다면, assign
은 publisher
로 부터 받은 값을 주어진 instance의 property에 할당할 수 있도록 합니다. 주어지는 값이 무조건 있어야하기 때문에sink
와는 다르게 publisher
의 Failure
타입이 Never
일때만 사용 가능 하다는 점! 기억해두세요✅
코드를 실행시켜보면 새로 받은 값을 object
의 value
에 할당하기 때문에 value
의 값이 바뀌면서 didSet
안에 있는 print
함수가 실행되고, 콘솔에는 다음과 같이 출력됩니다.
——— Example of: assign ———
Hello
world!
이렇게 편하게 사용할 수 있는 이유는 sink
와 마찬가지로 assign
을 사용할 때 그 귀찮은 protocol들을 모두 구현한 자체 subscriber
가 같이 제공되고 연결되기 때문이겠죠? 예상할 수 있듯이 subscriber
타입은 Assign
이에요~ Sink
와 마찬가지로 무제한 개수의 값을 요청합니다.
그런데 assign
의 parameter
값에서 이상함을 느끼지 않았나요?
.assign(to: \.value, on: object)
to
는 값이 할당될 property
자리고 on
은 해당 property
를 갖는 instance
의 자리 같은데… 도대체 to
안에 \.
는 왜 들어가는거야??
자 처음에 assign
은 새로운 값을keyPath
에 따라 주어진 인스턴스의 property
에 할당하는 것이라고 했죠? 그럼 \.value
가 keyPath
라는 것?! 맞습니다.👏🏻 \.
가 바로 object
의 property
를 특정하기 위해 사용하는 Key-Path Expression 이에요.
간단히 설명하자면 key-path 표현을 통해 type의 property를 나타낼 수 있습니다. \typeName.path
가 기본형이지만 타입 추론이 가능할때는 typeName
을 생략할 수 있습니다. \SomeObject.value
대신 \.value
를 사용한 것 처럼요! 자세한 내용은 링크를 확인하세용.
마무리
지금까지 Publisher
protocol 에서 제공하는 함수를 이용해서 보다 쉽게 구독 하는 방법에 대해 알아봤습니다! 잘 이해가 됐을지 모르겠네요 ㅠ
딱 봐도 subscriber
을 직접 구현하고 결과를 처리하는 것보다 저 함수들을 쓰는 일들이 많을 것 같지 않나요..?🧐 그러니까 잘 이해하고 넘어갑시다! ㅎㅎ 그럼..