겁나 오래 살아 남아 있는 iOS 개발자가 RxSwift와 Combine 처음 봤을 때
iOS 개발을 2009년 부터 시작한 고인물 개발자 입니다. 너어어어무 고전 기술? 로만 앱을 만들고 유지보수 해봐서 그런지 RxSwift와 Combine을 처음 봤을 때 솔직히 당황했습니다.
"이게 뭐지? 왜 이렇게 복잡하게 하지?"
혹시 저처럼 느끼신 분들을 위해 정리해봤습니다.
먼저, 둘의 관계
RxSwift
- 서드파티 라이브러리
- iOS 8부터 사용 가능
- 2015년부터 사용됨
Combine
- Apple 공식 프레임워크
- iOS 13 이상만 지원
- 2019년 WWDC에서 발표
결론부터 말하면, 개념은 거의 똑같고 이름만 다릅니다.
RxSwift를 알면 Combine은 쉽고, Combine을 알면 RxSwift도 쉽습니다.
용어 비교표
| 개념 | RxSwift | Combine |
|---|---|---|
| 데이터 발행 | Observable | Publisher |
| 데이터 구독 | Observer | Subscriber |
| 구독 해제 | dispose() | cancel() |
| 메모리 관리 | DisposeBag | Set<AnyCancellable> |
| 구독 메서드 | subscribe | sink |
| 값 래퍼 | BehaviorRelay | @Published |
기존 UIKit 방식 (우리가 익숙한 것)
버튼을 누르면 라벨 텍스트가 바뀌는 코드입니다.
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
@objc func buttonTapped() {
label.text = "눌렸다"
}
직관적이죠? 버튼 눌리면 → 함수 실행 → 라벨 변경
10년간 이렇게 해왔습니다.
RxSwift 방식
let disposeBag = DisposeBag()
button.rx.tap
.subscribe(onNext: { _ in
label.text = "눌렸다"
})
.disposed(by: disposeBag)
Combine 방식
var cancellables = Set<AnyCancellable>()
button.publisher(for: .touchUpInside)
.sink { _ in
label.text = "눌렸다"
}
.store(in: &cancellables)
나란히 비교하면 거의 똑같음
보시면 구조가 동일합니다.
| 단계 | RxSwift | Combine |
|---|---|---|
| 1. 이벤트 소스 | button.rx.tap |
button.publisher(for:) |
| 2. 구독 | .subscribe(onNext:) |
.sink { } |
| 3. 메모리 관리 | .disposed(by: disposeBag) |
.store(in: &cancellables) |
핵심 개념 3가지 (둘 다 동일)
1. Publisher / Observable (발행자)
데이터를 내보내는 쪽입니다.
"나 값 바뀌면 알려줄게"라고 선언하는 겁니다.
Combine:
@Published var name: String = ""
RxSwift:
let name = BehaviorRelay<String>(value: "")
2. Subscriber / Observer (구독자)
데이터를 받는 쪽입니다.
Combine:
$name
.sink { newName in
print("이름 바뀜: \(newName)")
}
RxSwift:
name
.subscribe(onNext: { newName in
print("이름 바뀜: \(newName)")
})
3. Operator (연산자)
중간에서 데이터를 가공합니다. 검색창 예시로 보겠습니다.
Combine:
$searchText
.debounce(for: 0.3, scheduler: RunLoop.main)
.filter { $0.count >= 2 }
.sink { text in
self.search(text)
}
.store(in: &cancellables)
RxSwift:
searchText
.debounce(.milliseconds(300), scheduler: MainScheduler.instance)
.filter { $0.count >= 2 }
.subscribe(onNext: { text in
self.search(text)
})
.disposed(by: disposeBag)
거의 똑같죠? debounce, filter 같은 연산자 이름도 동일합니다.
메모리 관리 비교 (중요!)
이거 빼먹으면 둘 다 동작 안 합니다.
Combine:
var cancellables = Set<AnyCancellable>()
$name
.sink { print($0) }
.store(in: &cancellables) // 필수!
RxSwift:
let disposeBag = DisposeBag()
name
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag) // 필수!
역할은 같습니다. 뷰컨트롤러가 해제될 때 구독도 같이 정리하는 거예요.
그래서 뭘 써야 하나요?
RxSwift를 써야 할 때:
- iOS 13 미만 지원해야 할 때
- 이미 RxSwift로 된 프로젝트 유지보수할 때
- RxCocoa의 UI 바인딩이 필요할 때
Combine을 써야 할 때:
- 새 프로젝트 시작할 때
- iOS 13 이상만 지원해도 될 때
- SwiftUI 쓸 때 (Combine 기반이라 궁합 좋음)
- 외부 의존성 줄이고 싶을 때
솔직한 후기
처음엔 "왜 이렇게 복잡하게?" 싶었습니다.
근데 검색창처럼 "입력 → 딜레이 → 필터 → API 호출" 같은 흐름은 확실히 코드가 줄어듭니다.
UIKit으로 같은 걸 만들려면 Timer 쓰고, 플래그 변수 만들고, 취소 로직 넣고... 더 복잡해요.
아직 완벽히 익숙하진 않지만, 하나씩 적용해보는 중입니다.
하나만 익히면 다른 하나는 자동으로 따라옵니다. 용어만 다르니까요.
위 내용은 클로드에게 물어가며 적어 본 내용입니다.
















































