[Combine] sink & assign

편하게 구독해보자✨

naljin
9 min readFeb 24, 2020

이전 포스팅에서는 직접 custom subscriber을 구현해보았습니다!

하지만 은 이렇게 Subscriber을 상속받아 직접 구현하는 것을 권장하지 않아요! 저걸 일일히 구현하는 것도 물론 귀찮은 일이고요! 그럼 어떻게하란 말이냐!!

Combine에서는 Sink, Assign 등의 기본 Subscriber를 제공하고 있고 Publisher protocol에 정의된 sinkassign 함수를 이용해서 각각의 subscription을 간단히 구현할 수 있답니다🎉 오늘은 이 함수들에 대해 알아보아요🚀

만약 subscribersubscribe에 대한 개념이 잘 안잡힌 상태에서 바로 이 글을 보시면 이해가 잘 안될 수 있어요! 왜냐면 제가 본 책에서 바로 예제에 sink 부터 때려버리길래 ㅎ..? 뭐라고요..? 했던 경험이 있거든요ㅠ 그래서 저는 기본 개념들부터 정리하고 sinkassign 은 뒤로 빼서 작성합니다✍🏻 만약 읽다가 이해가 안되시면 이전 글부터 읽고 오시는걸 추천드려요

그럼 시작!

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를 구독하는 방법 기억나시나요?? publishersubscriber 연결하는 subscription 만드려고 아래 코드를 사용했어요.

publisher.subscribe(subscriber)

아니 근데 파라미터로 들어가는 subscriber을 먼저 만들어야하네..? Subscriber protocol 받아서 Input, Failure 타입 지정해야하지.. subscription 받을 때.. value 받을 때.. completion 받을 때 등등 Subscriber protocol 로 정의되어 있는 함수 일일히 안에서 구현해야하지.. 아주 귀찮은 일이 많았단 말이죠?

하지만 sink를 보십쇼. 그런거 다 필요 없고 valuecompletion 의 새로운 값만 편하게 받아서 처리할 수 있습니다!

——— 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(_:)을 호출하여 값을 최대로 얼만큼 받을 것인지 설정합니다.

따라서 Sinkreceive(subscription:)구현부에는 이런 코드가 들어가있을거라는걸 예측할 수 있을거예요!

subscription.request(.unlimited)  

이렇게 sink(receiveCompletion:receiveValue:) 를 사용하는것 만으로 우리는 publishersubscriber(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 가 그런 과정들 다 때려치우고 간단히 완료와 값에 대한 이벤트를 처리할 수 있게 해준다면, assignpublisher로 부터 받은 값을 주어진 instance의 property에 할당할 수 있도록 합니다. 주어지는 값이 무조건 있어야하기 때문에sink와는 다르게 publisherFailure 타입이 Never일때만 사용 가능 하다는 점! 기억해두세요✅

코드를 실행시켜보면 새로 받은 값을 objectvalue 에 할당하기 때문에 value의 값이 바뀌면서 didSet 안에 있는 print 함수가 실행되고, 콘솔에는 다음과 같이 출력됩니다.

——— Example of: assign ———
Hello
world!

이렇게 편하게 사용할 수 있는 이유는 sink와 마찬가지로 assign을 사용할 때 그 귀찮은 protocol들을 모두 구현한 자체 subscriber가 같이 제공되고 연결되기 때문이겠죠? 예상할 수 있듯이 subscriber 타입은 Assign 이에요~ Sink 와 마찬가지로 무제한 개수의 값을 요청합니다.

그런데 assignparameter값에서 이상함을 느끼지 않았나요?

.assign(to: \.value, on: object)

to 는 값이 할당될 property 자리고 on은 해당 property를 갖는 instance의 자리 같은데… 도대체 to 안에 \.는 왜 들어가는거야??

자 처음에 assign새로운 값을keyPath에 따라 주어진 인스턴스의 property에 할당하는 것이라고 했죠? 그럼 \.valuekeyPath 라는 것?! 맞습니다.👏🏻 \. 가 바로 objectproperty를 특정하기 위해 사용하는 Key-Path Expression 이에요.

간단히 설명하자면 key-path 표현을 통해 type의 property를 나타낼 수 있습니다. \typeName.path 가 기본형이지만 타입 추론이 가능할때는 typeName을 생략할 수 있습니다. \SomeObject.value 대신 \.value 를 사용한 것 처럼요! 자세한 내용은 링크를 확인하세용.

마무리

지금까지 Publisher protocol 에서 제공하는 함수를 이용해서 보다 쉽게 구독 하는 방법에 대해 알아봤습니다! 잘 이해가 됐을지 모르겠네요 ㅠ

딱 봐도 subscriber을 직접 구현하고 결과를 처리하는 것보다 저 함수들을 쓰는 일들이 많을 것 같지 않나요..?🧐 그러니까 잘 이해하고 넘어갑시다! ㅎㅎ 그럼..

이전 포스팅 👈🏻

다음 포스팅 👉🏻

참고

--

--