Combine을 살펴보면 RxSwift에서 용어만 바뀐 느낌이 든다.
예를 들어
RxSwift에서의 Observable은 Combine에서 Publisher이고,
Observer는 Subscriber
Disposable은 AnyCancellable
...
이런 식으로 말이다.
물론 더 깊이 들어가면,
RxSwift는 class기반이고, Combine은 protocol 기반,
Combine에서는 에러 대응을 항상 해줘야 함.. 등
차이점은 더 있을 수 있겠으나, 대부분 RxSwift와 1:1로 대응해서 생각하면 이해하기는 어렵지 않았다.
그런데, 그 중 궁금증이 생긴 부분이 있었다.
RxSwift에서의 `.disposed(by: disposeBag)` 부분에 해당하는 코드가
Combine에서는 `.store(in: &cancellables)` 과 같은 형태로 쓰이는것을 보고
cancellables라는 변수가 inout파라미터로 쓰이는 이유가 궁금해졌다.
이번 글에서는 cancellables가 inout파라미터로 쓰이는 이유에 대해서만 간단히 알아보겠다.
Combine코드의 구성
어떤 ViewController에서 viewDidLoad시점에 네트워크 통신을 한다고 가정해보자.
그럼 해당 뷰컨의 ViewModel에서는 다음과 같은 코드가 쓰여있을 것이다.
class CombineViewModel {
// Rx disposeBag
var cancellables = Set<AnyCancellable>()
struct Input {
// Rx PublishSubject
let viewDidLoadTrigger = PassthroughSubject<Void, Never>()
}
struct Output {
// Rx BehaviorSubject
let list = CurrentValueSubject<String, NetworkError>("")
}
init() {
let input = Input()
input.viewDidLoadTrigger
.sink { [weak self] _ in // Rx subscribe
//callRequest
}
.store(in: &cancellables) // Rx dispose
}
func callRequest() {
//Network 통신
}
}
Rx와 대응해본다면
.sink는 .subscribe에 해당하니
Disposable을 반환할테고, 이는 Combine에서는 AnyCancellable을 반환할 것이다.
그리고 Rx에서는 DisposeBag의 인스턴스인 disposeBag에 담을 것인데...
Combine에서는 `Set<AnyCancellable>`의 인스턴스인 `cancellables`이라는 변수를 인아웃 파라미터로써 .store라는 메서드에 넣어주고 있다.
먼저 inout파라미터란 무엇인지 알아보자.
inout parameter
먼저 inout파라미터에 대해 간단히 알아보도록 하자.
다음과 같은 메서드가 있다고 해보자.
func add(number: Int) {
number += 1
}
add(number: 3)

메서드의 파라미터는 let이라서 값 자체를 바꿀 순 없다.
그래서 위와같은 오류가 발생하는 것이다.
그래서 일반적으론 메서드 내에 별도의 변수를 만들어서 같은 동작을 구현했었다.
func add(number: Int) {
var value = number
value += 1
}
그리고, 파라미터는 값을 복사한다.
그래서 만약 메서드 내에서 값을 변경하는 동작이 가능하다고 하더라도!
메서드 밖의 변수에는 변화가 없다.
func add(number: Int) {
number += 1 // 이게 가능하다고 가정하더라도
}
var data = 3
add(number: data)
print(data) // 3 => 변화 없음
반면, inout 파라미터로 받게 된다면, 파라미터가 값 형태가 아니라 참조 형태로 전달되게 된다.
즉, 메서드 내에서 수정한 내용이 밖의 변수에 영향을 끼칠 수 있다.
// inout매개변수는 파라미터가 참조 형태로 전달 => 함수 내부에서 수정한 내용이 data 변수에도 반영이 됨
func add(number: inout Int) {
number += 1
}
var data = 3
add(number: &data)
print(data) // 4 => 파라미터로 들어간 data가 바뀜!
inout 파라미터는 inout 키워드를 붙여주고, 아규먼트에는 &기호를 붙이므로써 참조를 전달하겠다고 표시한다.
그렇다면, 다시 Combine으로 돌아와서 왜 cancellables는 인아웃 파라미터로 전달될까?
Set<AnyCancellable>
cancellables는 Set<AnyCancellable>의 인스턴스이다.
그리고, Set에서 알 수 있듯이, Set<AnyCancellable>는 값타입이다.
`store(in:)`메서드의 선언을 살펴보면 다음과 같다.

값타입인 Set<AnyCancellable>을 inout 파라미터로 받고있는 것을 볼 수 있다.
`.sink` 메서드는 Rx의 subscribe에 해당하는 메서드로, AnyCancellable을 반환한다.

결국 이 AnyCancellable의 인스턴스를 저장해놓아야 구독을 유지할 수 있다.
`store(in:)`의 내부 동작은 다음과 같다.
extension AnyCancellable {
func store(in set: inout Set<AnyCancellable>) {
set.insert(self)
}
}
- AnyCancellable객체를 Set<AnyCancellable>에 추가함
- 즉, cancellables라는 Set에 새로운 구독을 추가하여 저장하는 형태
그런데 여기서 Set은 값타입이고, store 메서드에서 새로운 AnyCancellable를 추가하려면 원본인 Set를 직접 수정해야 한다
그래서 값이 아니라 참조를 복사하는 inout 파라미터를 사용하는 것이다.
'iOS > Swift' 카테고리의 다른 글
| [iOS/Swift] 컴파일러 최적화 관점에서 본 Opaque Type (0) | 2025.03.28 |
|---|---|
| [iOS/Swift] Kingfisher를 쓰지 않고 이미지 캐싱 구현하기 - NSCache (0) | 2025.03.25 |
| [iOS/Swift] DI와 DIP는 다르다! - 의존성 주입 알아보기 (1) | 2025.03.18 |
| [iOS/Swift] 책임분리를 위한 설계 - DTO와 Entity에 대해 (0) | 2025.03.15 |
| [iOS/Swift] Realm의 데이터 삭제하기 (0) | 2025.03.12 |