안녕하세요 !
오늘은 Combine(1) 에 이어서 Combine을 구성하는 각 요소와 사용 방법 및 예시 등에 대해 공부한 내용을 작성해보고자 합니다.
저는 이론 뿐인 지식은 별로 좋아하지 않기 때문에 바로 써먹을 수 있게 저만의 정리를 하는 걸 좋아하는 편입니다.
혹시 이해가 되지 않는 부분들은 다른 블로그, Apple document, GPT 등을 통해서 자신만의 지식으로 바꾸는 것을 추천드립니다.
시작은 기본 구성 요소에 대해 작성할 것인데, 처음이니 기본적인 의미와 이름을 숙지하는데 집중해주세요.
시작합니다.
Combine 의 기본 구성 요소
Level 1
Publisher
이벤트를 발생시키는 객체로, 데이터를 발행하여 Subscriber에게 전달하는 역할을 합니다.
더 쉽게 말하자면, "데이터를 전달하는 누군가에게 전달하는 객체" 쯤으로 이해해주시면 편할 것 같습니다.
Subscriber
위의 Publisher에서 발행한 데이터를 수신하고 처리하는 역할을 합니다.
더 쉽게 말하기도 힘들지만, Publisher 가 보낸 데이터를 받고 처리하는 역할로 이해해주시면 될 것 같아요
보통 구현을 할 때, Publisher 라는 객체를 만들고 뒤에 .sink 또는 .assign 클로저를 활용하여 수신을 표현합니다.
그렇기 때문에 Publisher는 명시적으로 객체를 만들지만, Subscriber는 변수를 만들어서 잘 사용하진 않습니다. (물론 커스텀은 가능해요)
Level 2
Operator
데이터 스트림을 변환하고, 필터링 하거나 결합하는 중간 과정 처리 단계 입니다.
구현 시, Publisher가 데이터를 발행하는 과정에서 해당 Operator를 사용해서 원본 데이터를 조작하여 발행할 때 주로 사용됩니다.
map, reduce, filter 와 같은 함수형 프로그래밍 연산자로 이뤄져 있어 보통 클로저를 통해 동작을 처리합니다.
Cancellable
Publisher가 발행한 정보를 Subscriber가 수신한 후, 책임을 다하여 둘 사이의 연결을 끊을 때 사용합니다.
만약 사용하지 않는 combine 관계를 계속 유지한다면 메모리가 터질지도 모릅니다.
즉, Memory leak을 방지하기 위해서라도 꼭 필요한 단계입니다.
구현 시, Subscribe 쪽에서 수신 연결을 할 때, 해당 객체를 Cancllable 객체에 store해서 수신이 끝나면 cancle 하도록 설정합니다.
ViewController 혹은 ViewModel 에서 Cancellable을 관리할 때는 Subscriber를 Store 해주고, 참조 관계만 잘 관리해주면 deinit 시 자동으로 binding을 해제해줍니다. ( Combine (3) 에서 깊게 다룹니다. )
Scheduler
스케쥴러는 비동기 데이터 스트림과 관련된 곳이라면 정말 빠지지 않고 등장하죠.
저희가 Combine에서 Scheduler를 사용하는 목적은 주로 비동기 데이터 스트림 관리(송/수신)를 할 때, 어떤 스레드에서 동작하게 할지 설정하는 것 입니다.
주로 UI 관련 동작들은 대체로 Main Thread로 설정하게 됩니다.
구현 시, Subscribe 쪽에서 수신 연결을 할 때, 특정 Thread를 지정하여 설정합니다.
Level 3
Subject
일단 이 녀석이 공부하면서 가장 골치가 아팠던 녀석입니다.
이 친구를 설명하는 글을 보면 정말 애매하기 때문이죠.
"Publisher 중, 외부에서 데이터를 발행할 수 있는 특별한 Publisher 이다" 라고 표현을 하더군요
일단 이해가 잘 되지 않아 '외부' 가 표현하는 범위를 알아보기로 했습니다.
Publisher에서 외부가 표현하는 범위는 자신이 발행할 때를 내부, 발행시켜 수신 후를 외부라고 표현하더군요.
즉 수신이 1회 완료된 이후에도 송신이 가능한 객체를 의미하는 것입니다.
반대격인 Publisher로 Just, Future라는 Publisher가 있습니다. (이 객체들은 딱 한번만 송신이 가능하고 외부에서는 불가능합니다.)
기본 예제 코드
Level 1
Publisher
1. arrayPublisher
주석에 적힌대로 배열의 요소들을 순서대로 발행하는 publisher 입니다.
.delay 클로저 함수를 통해 scheduler와 간격을 정하면 정해진 간격에 따라 수신할 수도 있습니다.
2. Just Publihser
앞에 나왔던 publisher로 단 한번만 수신을 하고 송신이 종료되는 publisher 입니다.
3. Defferd Publisher
설명으로는 값 발행을 지연할 수 있는 Publisher라고 하는데,
값 발행을 지연하는 방법은 수신이 들어왔을 때, 그와 동시에 Publisher 객체를 메모리에 생성한다고 하네요.
음.. 메모리 절약을 위해 만들어진 publisher 같은데 일반 publisher 들과의 차이는 이게 다 인듯 합니다.
4. Future Publisher
클로저 안에서 비동기 수행이 이뤄지고, 끝났을 때 promise에 결과 값을 반환하면 결과 값이 송신되는 구조의 publisher 입니다.
보통 비동기 처리를 할 때, 딱 한번만 처리를 원하면 Future Publisher를 사용하고
여러번 update하여 처리가 필요할 때는 passThroughSubject 객체를 활용하여 처리 합니다.
Subscriber
일단 결과 값 출력을 위해서 sink 클로저 메서드를 이용해서 subscribe 부분을 구현했습니다.
하지만 수신 결과로 특정 데이터에만 값을 할당하는 경우에는 assign 클로저 메서드를 활용하여 구현할 수 있습니다.
Level 2
Operator
arrayPublisher를 수신할 때, 위에서 말한 3개의 함수형 operator를 다 넣어서 실행해봤습니다.
서버에서 데이터를 가공(결측치 제거, 최신순 정렬, 데이터 합 등)을 할 때, 매우 유용하게 쓰일 것 같습니다.
map -> 2, 4, 6, 8, 10
filter -> 2, 4
reduce -> 6
위와 같은 로직으로 결과 값이 6이 되고 complete 된 걸 보실 수 있습니다.
Cancellable
Set으로 저장하는 이유는 당연히 한번에 같은 두 개의 구독이 일어나는걸 방지하기 위해서 입니다.
publisher 구조 상 그럴 수도 없지만요
Apple은 항상 코드를 2중 3중으로 보호하는 느낌입니다.
이후 수신 과정 중, 구독/수신 관계을 cancellables 변수에 저장하도록 했습니다.
이유는 당연히도 구독을 취소해주기 위함입니다.
예시 코드 이기 때문에 이렇게 작성을 했지만,
Cancellable 의 본 목적은 메모리 누수 관리에 있습니다.
예를 들어 특정 ViewController가 목적을 다하고 pop 되는 상황을 가정해봅시다.
해당 ViewController 에는 사용한 ViewModel 객체와 연결된 binding 파트 (publish, subscribe)가 존재할 것 입니다.
만약 앱이 끝날 때 까지, 해당 뷰에 지속해야하는 관계라면 취소해주지 않아도 되지만
일회성으로 사용하는 View들은 무조건 해당 binding 관계를 취소해주어야 합니다.
왜냐하면 두 연결 관계가 사라지지 않으면 publisher와 subscriber는 계속해서 메모리 어딘가에 사라지지 않고 남아있을 테니까요 !
-> 정확한 이유는 Reference 개념을 알면 설명이 가능한데, iOS의 자동 메모리 관리(ARC)에 대해 공부하시면 알게 됩니다.
그렇기 때문에 viewDidDisappear override method 등에 해당 처리를 해주면 깔끔하게 처리가 될 것 같습니다.
Scheduler
여기에선 2가지 스케쥴링 방식이 등장합니다.
1. subscribe(on: ) : 수신 받는 곳의 Thread를 지정
2. receive(on: ): 송신 받는 곳의 Thread를 지정
예를 들어,
firebase에서 특정 데이터를 불러와 UI 를 update하는 기능이라고 생각해봅시다.
그렇다면 firebase에서 특정 데이터를 불러오는 부분은 데이터를 수신하는 부분이 될 것입니다.
thread는 상황에 따라 다르지만 백그라운드에서 돌려주는것이 좋습니다.
왜냐하면 main thread에서 해당 코드를 돌리면 firebase에서 데이터를 불러오는 시간 동안 UI Blocking 이 일어나기 때문입니다.
반대로 데이터를 불러와서 UI를 변경하는 것은 Main Thread에서 일어나는게 좋습니다.
왜냐하면 데이터를 받아오는 즉시 UI에 수정이 반영되어야하고, 해당 동작은 시간이 오래 걸리는 작업이 아니기 때문입니다.
(데이터를 불러오는 것에 비해)
Level 3
Subject
Subject로 publisher와 다른 publisher들과의 차이점은 발행 후에도 지속적으로 값을 보낼 수 있다는 점 입니다.
바인딩을 계속 새롭게 하여 송신을 계속 새로받는 형식보다는 처음 바인딩 한 후, 새로운 값이 들어오면 같은 바인딩으로 송신받는게 훨씬 효율적이기 때문에 !
새로운 값이 지속적으로 들어오고, 그걸로 송신 처리를 해야하는 경우 subject publisher를 선택하는것은 매우 타당한 선택이 될 수 있습니다.
또한 subject는 2가지 종류로 나뉘는데
1. PassthroughSubject : 초기 발행 값이 없는 subject
2. currentValueSubject: 초기 발행 값이 존재하고, 값을 송신 받을 때 마다 값을 저장하는 subject
즉, 단발성 이벤트를 계속해서 처리하는 부분이라면 1번,
초기 상태를 바탕으로 상태 관리를 지속적으로 해줘야 하는 부분에서는 2번 을 사용하면 되지 않을까 싶습니다.
Combine 기본 편 마무리
네 이렇게 Combine의 아주아주 기초적인 개념들을 다뤄봤습니다.
늘 느끼는 거지만 모순적이게도 기본적인 내용들에 대해 다루다보면 점점 더 깊숙하고 어려운 내용들이 튀어나오는 것 같습니다.
왜 이런게 만들어졌고, 어디에 써야하고 등을 생각하게 되서 더욱 그런 것 같습니다..
그렇지만, 위의 코드들은 아주 기초적이고 기본적인 사용법이기 때문에 실제 프로젝트에서 어떤식으로 적용되는지를 알아봐야 할 필요가 있습니다.
다음 포스팅에서는 MVVM + UIKit + Combine 을 실현할 수 있는 프로젝트를 만들어서 포스팅 해 볼 생각입니다.
제 최종 목표는 위와 같은 구조를 숨쉬듯 편하게 구현할 수 있게 하여, 제가 만든 앱인 "Ro_ad"에 해당 DI(Degisn Architecture) MVVM을 적용해보는 것입니다 !
오늘 포스팅은 여기까지 입니다.
다들 공부 화이팅 입니다 ~
Mins 주인장 올림
'iOS > iOS' 카테고리의 다른 글
[iOS] Combine (4) (6) | 2024.11.11 |
---|---|
[iOS] Combine (3) (8) | 2024.11.08 |
[iOS] Combine (1) (6) | 2024.11.05 |
[iOS] Frame & Bounds (2) - Bounds (0) | 2023.02.13 |
[iOS] Frame & Bounds (1) - Frame (4) | 2023.02.13 |