아요 개발 일기
[Combine] - Publisher, Subscriber 본문
안녕하세요 소진입니다!
요즘 SwiftUI를 계속해서 공부하고있는데,
Combine은 무조건무조건 필수로 알아야겠더라구요!
RxSwift하느라 이제야 제대로 살펴보네요..
대충 사용은 해봤는데 정확히 알고 있는 것 같지는 않아서 깊게 들여다보려고
이렇게 포스팅하게 되었습니다!
많이 늦었지만 그만큼 빡세게 해보겠습니다 ㅎ
Combine ? 🧐
apple에서 설명하는 combine은
"이벤트 처리 연산자를 결합하여 비동기 이벤트 처리를 Customize"
하는 Framework입니다.
https://developer.apple.com/documentation/combine
음..
비동기 이벤트처리하는 프레임워크인가?
Overview를 보면서 더 자세히 알아봅시다!
Combine 프레임워크는 시간에 따라 값을 처리하기 위한 선언적(declarative) Swift API를 제공합니다.
이러한 값들은 다양한 종류의 비동기 이벤트를 나타낼 수 있습니다.
Combine은 시간이 지남에따라 변경될 수 있는 값을 노출하도록(expose) 선언하고
subscribers는 publishers로부터 해당 값들을 받도록 선언합니다.
오? 그러니까,
RxSwift와 비슷하게 비동기 처리를 하는 프로토콜이네요!
그렇다면 Combine을 사람들이 그렇게 많이 사용하는 이유가 뭘까요??
이벤트 처리 코드를 중앙 집중화(centralizing)하고 중첩된 closures 및 콜백과 같은
까다로운 기술을 제거하여 코드를 더 쉽게 읽고 유지 관리할 수 있습니다.
오호라..^^..
자, 그럼 본격적으로 주요 컴퍼넌트를 알아볼까요??!!
Publisher
Publisher는 프로토콜이며 이름 뜻으로 보면 발행자? 역할을 하는 친구 같죠?
정의와 OverView를 봅시다!
타입이 시간에 따라 일련의 값을 전송 할 수 있음을 선언합니다.
Publisher는 하나 이상의 Subscriber 인스턴스에 element를 제공합니다.
Subscriber Input과 Failure 유형은 Publisher가 선언한 output과 Failure에 일치해야합니다.
Publisher는 Subscriber을 accept하는 OutputFailurereceive(subscriber:) 메소드를 구현합니다.
Subscriber
Subscriber은 구독자!겠지요?
Publisher로부터 input을 받을 수 있는 형식을 선언하는 프로토콜입니다
Subscriber의 인스턴스는 Publisher으로부터 element stream과
그들의 relationship의 변경사항을 설명하는 life cycle 이벤트를 받습니다.
뭐..
정의는 알았으니,
직접 사용하러 갑시다!!!
실습하기
Publisher
발행?할 수 있는 방법은 두가지 입니다!
let publisher = Just(sojin)
Just를 사용하면 단일(하나) 데이터를
let arrayPublisher = [1, 3, 5, 7, 9].publisher
.publisher를 사용하면 여러개 데이터를 발행?할 수 있습니다!
Subscriber
1. sink
2. subscribe
3. assign(to: on:)
sink를 많이 사용하니까 sink 먼저 알아보겠습니다!
1. sink
sink는 아래 이미지와 같이 두가지 있습니다!!
위에꺼는 그냥 데이터만 받고
밑에는 데이터도 받고 완료했을때 처리도 해줍니다!
코드로 봅시당
let subscription = publisher.sink { value in
print("Received Value: \(value)")
}
let subscription2 = arrayPublisher.sink { value in
print("finished")
} receiveValue: { value in
print("Received ArrayValue: \(value)")
}
[출력]
Received Value: sojin
Received ArrayValue: 1
Received ArrayValue: 3
Received ArrayValue: 5
Received ArrayValue: 7
Received ArrayValue: 9
finished
간단하죠?!
아래처럼
publisher 선언부에서 바로 sink를 호출도 가능합니다!
let subscription = Just("Hello").sink { value in
print("바로 쓰기! : \(value)")
}
[출력]
바로 쓰기! : Hello
2. subscribe
subscribe의 파라미터 타입은 Subscriber인데,
Subscriber은 프로토콜이라고 위에서 설명했죠???
Subscriber 프로토콜을 지정해주면
위 이미지처럼 요구하는 프로퍼티와 메소드가 있습니다.
여기서 Input 은 subscriber가 받는 데이터의 타입을 적어주면되고
Failure은 subscriber가 받을 수 있는 Error의 종류를 말합니다.
(subscribe가 Error를 받을 수 없는 경우 Never을 적으면 됩니다)
Subscriber의 Overview를 보면 receive 메서드를 호출하여 사용하는 걸 볼 수 있는데,
코드로 직접 사용해보겠습니다! :)
class SojinSubscriber: Subscriber {
typealias Input = Int
typealias Failure = Never
// 1. Subscriber에게 Publisher를 성공적으로 구독했으며 item을 요청할 수 있음을 알림
func receive(subscription: Subscription) {
print("구독 시작")
subscription.request(.unlimited)
}
// 2. Subscriber가 받을 것으로 예상하는 추가 요소 수를 나타내는 인스턴스
func receive(_ input: Input) -> Subscribers.Demand {
print("\(input)")
return .none
}
// 3. Subscriber에게 Publisher가 정상적으로 또는 오류와 함께 게시를 완료했음을 알립
func receive(completion: Subscribers.Completion<Never>) {
print("완료", completion)
}
}
arrayPublisher.subscribe(SojinSubscriber())
[출력]
구독 시작
1
3
5
7
9
완료 finished
아하...!
데이터 진행 과정을 알 수 있겠네요
그럼..
.unlimited와 .max 같은건 뭘까요?
.max(Int) : 최대로 보여줄 itme 수
.unlimited : Publisher가 elements 모두
.none : Publisher의 elements 없음 (출력 안 함)
각 필요에 따라서 선택하면 됩니당
아래와 같이 Switch case 문을 사용해서 응용도 가능합니다!
class SojinSubscriber: Subscriber {
typealias Input = Int
typealias Failure = Never
//1. 구독자에게 게시자를 성공적으로 구독했으며 항목을 요청할 수 있음을 알림
func receive(subscription: Subscription) {
print("구독 시작")
subscription.request(.max(1))
}
//2. 구독자가 받을 것으로 예상하는 추가 요소 수를 나타내는 인스턴스
func receive(_ input: Input) -> Subscribers.Demand {
print("\(input)")
switch input {
case 1:
return .max(1)
default:
return .none
}
}
//3. 구독자에게 게시자가 정상적으로 또는 오류와 함께 게시를 완료했음을 알립니다.
func receive(completion: Subscribers.Completion<Never>) {
print("완료", completion)
}
}
[출력]
구독 시작
1
3
1,2 번 두개다 max(1)이면,
element가 1개만 나오면 될 것 같은데
왜 2가 나오지? 라고 생각할 수 있습니다!
이유는
1번에서 먼저 1개를 출력하여 0번째는 무조건 출력이되겠죠?
그러면 다음 데이터는 1번째니까 case 1에 걸리게되고
max(1)이 실행됩니다!
그래서 1(0번째)과3(1번째)이 출력되는거죠!
즉, 1번과 2번 실행이 중첩되서 데이터가 나오는 겁니다!
그래서 case문에 0 혹은 2 이상의 수가 들어가면
구독 시작
1
위와같은 출력이 나와요!
1번째가 해당하지 않기때문에 default에 걸리거든요!!!!
이해.. 가시나요?,,,
sink에서도 비슷한 작업을 할 수 있는 메서드가 있는데요!
sink(receiveCompletion: ) 입니다ㅎㅎ
코드를 봅시다!
let subscription3 = publisher.sink (receiveCompletion: { (result) in
switch result {
case .finished:
print("finished")
case .failure(let error):
print(error.localizedDescription)
}
}, receiveValue: { (value) in
print(value)
})
[출력]
sojin
finished
sink는 클로저 기반으로 동작하네요!!
susubscribe나 sink는
각자 상황에 맞는 거 사용하면 될 것 같아요 ㅎㅎ
3. assign
휴.,, 드디어 assign,,
class MyClass {
var property: Int = 0 {
didSet {
print("Did set property to \(property)")
}
}
}
let object = MyClass()
let subscription4 = arrayPublisher.assign(to: \.property, on: object)
print("Final Value: \(object.property)")
[출력]
Did set property to 1
Did set property to 3
Did set property to 5
Did set property to 7
Did set property to 9
Final Value: 9
좀 복잡하죠??
위의 arrayPublisher을 MyClass에 있는 property에 할당해주는 방식입니다!
Subscriber & Publisher Pattern
위 사진처럼 Publisher와 Subscriber간의 통신이 이루어집니다!!
너무 길었죠..
고생 많으셨습니다!
다음은 Subject에 대해 알아보도록하겠습니다!!ㅎㅎ
코드
https://github.com/sojin2/Combine
'iOS > Combine' 카테고리의 다른 글
[Combine] - Subject (0) | 2023.01.16 |
---|