SwiftUI와 Combine, 둘을 함께 사용하면 말 그대로 iOS 앱 개발이 ‘한결 쉬워지고 강력해집니다’. 코드량은 줄고, 유지보수는 쉬워지고, UI는 실시간으로 쫙쫙 바뀌는 맛에 빠지게 되죠. 이 포스팅에서는 SwiftUI의 선언형 UI 구조와 Combine의 반응형 데이터 흐름이 어떻게 ‘찰떡궁합’을 이루는지, 실전 예제와 함께 자연스럽고 솔직하게 풀어볼게요.
- SwiftUI는 상태 기반으로 UI를 갱신해주는 똑똑한 프레임워크입니다.
- Combine은 비동기 데이터 흐름을 선언적으로 연결해주는 툴이에요.
- 두 기술을 함께 쓰면 복잡한 비동기 처리를 직관적으로 구현할 수 있어요.
- 실제 코드로 Timer 카운터를 만들어보면 얼마나 간단한지 알 수 있죠.
- Combine 없이도 SwiftUI 5에서는 Observation 프레임워크로 간편화가 가능해졌어요.
SwiftUI가 왜 뜨거운지부터 짚고 가보죠
SwiftUI는 2019년에 처음 등장했을 때만 해도 ‘어린 티가 난다’는 소리를 들었지만, 이제는 얘기가 다릅니다. 애플 생태계 전체에서 ‘선언형 UI’의 표준으로 자리잡았다고 봐도 무방해요. 핵심은 이거죠. 뷰(View)가 상태(State)에 반응한다는 것. 코드를 다시 빌드하거나 수동으로 갱신하지 않아도, 상태가 바뀌면 UI가 자동으로 리렌더링되니까요.
예전에는 버튼을 누르면 “buttonTapped()” 같은 액션을 만들어야 했고, 상태도 직접 뷰에 반영해야 했죠. 그런데 SwiftUI에서는 `@State` 하나면 끝나요. 상태만 바꿔주면 화면이 알아서 변하니까 말이죠.
Combine이 필요한 이유? 비동기 이벤트는 얘한테 맡기세요
비동기 작업 많으시죠? API 요청, 타이머, 유저 입력 이벤트 처리, 스크롤 감지 등등… 이걸 매번 콜백, 델리게이트, NotificationCenter로 하려면 정말 ‘숨이 턱’ 막히죠. Combine은 이걸 스트림 기반으로 정리해주는 똑똑한 도구예요.
- Publisher: 데이터를 발행하는 주체. 예: URLSession, Timer 등
- Subscriber: 그 데이터를 받아 처리하는 객체
- Operator: 중간에서 데이터를 가공하거나 필터링하는 조작자. 예: map, debounce, filter 등
이걸 조합하면, 예를 들어 “0.3초 동안 입력이 없으면 서버 요청 날리기” 같은 로직도 단 두 줄로 처리돼요. SwiftUI와 함께 쓰면 UI와 데이터 흐름이 정말 ‘찰떡’처럼 붙습니다.
실전 예제: 타이머 카운터 앱을 만들어볼게요 ⏱️
class TimerViewModel: ObservableObject {
@Published var count: Int = 0
private var timerCancellable: AnyCancellable?
func startTimer() {
timerCancellable = Timer
.publish(every: 1.0, on: .main, in: .default)
.autoconnect()
.sink { [weak self] _ in
self?.count += 1
}
}
func stopTimer() {
timerCancellable?.cancel()
}
}
struct TimerView: View {
@StateObject private var viewModel = TimerViewModel()
var body: some View {
VStack {
Text("카운트: \(viewModel.count)")
.font(.largeTitle)
HStack {
Button("Start") {
viewModel.startTimer()
}
Button("Stop") {
viewModel.stopTimer()
}
}
}
}
}
이 예제가 단순해 보여도 SwiftUI + Combine의 ‘맛’을 제대로 보여줘요. @Published 속성이 바뀌면 SwiftUI는 자동으로 UI를 갱신하니까, 카운트 값이 증가할 때마다 Text 뷰가 다시 렌더링되죠.
Combine을 쓸 때 주의할 점도 있어요
- 메인 스레드에서 UI 갱신: URLSession 같은 비동기 작업은 .receive(on: RunLoop.main)을 꼭 붙여줘야 해요.
- AnyCancellable 관리: 구독은 Set로 잘 보관해두세요. 안 그러면 바로 취소됩니다.
- 메모리 누수 조심: [weak self] 안 쓰면 ViewModel이 메모리에 남아있을 수 있어요.
SwiftUI 5부터는 Combine 없어도 되냐고요? 반은 맞고 반은 틀렸어요
최근 SwiftUI 5부터는 Observation이라는 새로운 상태 관리 방식이 도입됐어요. @Observable 매크로 하나로 ViewModel을 선언하면, 프로퍼티가 바뀔 때마다 자동으로 뷰가 갱신돼요. @StateObject, @EnvironmentObject 없이도 상태 공유가 되죠. 예전보다 코드가 훨씬 간결해졌습니다.
하지만, Combine이 ‘완전히’ 필요 없어졌다는 건 아닙니다. 특히 네트워크 처리, 사용자 입력 debounce 처리, 타이머 등 스트림이 필요한 경우엔 Combine이 여전히 유효해요. async/await와도 잘 섞어 쓸 수 있으니 상황에 따라 유연하게 골라 쓰는 게 중요하죠.
Combine + SwiftUI가 만들어주는 사용자 경험
이 조합이 진짜 강력한 건, 결국 ‘사용자 경험’이 달라지기 때문이에요. 사용자 입력이 들어오자마자 실시간으로 필터링된 리스트를 보여주고, 서버 응답이 오면 바로 뷰가 바뀌는 거죠. 예전엔 이걸 위해 콜백, 델리게이트, 상태 관리 객체까지 잔뜩 써야 했는데, 이제는 단 몇 줄로 처리됩니다.
결국, 이 모든 건 **“반응성”**이라는 키워드로 귀결됩니다. 사용자의 액션에 즉각적으로 반응하는 UI, 중복 없이 깔끔하게 처리되는 데이터 흐름, 그리고 유지보수가 쉬운 구조. SwiftUI와 Combine은 이걸 가능하게 해줍니다.
개발자라면 한 번쯤은 고민하게 되는 질문
“SwiftUI만으로 충분한가요, 아니면 Combine까지 배워야 할까요?”
제 생각엔 이렇습니다. 간단한 앱, 특히 로컬에서만 동작하는 앱이라면 SwiftUI만으로도 충분합니다. 하지만 비동기 작업이 많고, 네트워크 요청 처리나 사용자 이벤트 스트림이 중요한 앱이라면 Combine은 반드시 익혀야 해요. 선택이 아니라 필수에 가깝다고 봐요.
마무리하며… SwiftUI와 친해지고 싶다면 Combine은 필수 친구예요
이 두 도구는 서로의 빈틈을 잘 메워주는 ‘찐친’ 관계입니다. SwiftUI가 UI를 간결하게 만들어준다면, Combine은 데이터 흐름을 구조화해줘요. 그리고 이 둘을 잘 다루면, 코드가 적어지고 기능은 더 늘어나죠. 진짜 iOS 개발의 미래를 경험하고 싶다면, 꼭 두 가지를 함께 사용해보세요.
처음에는 개념이 어려워 보일 수 있지만, 한 번 익숙해지면 그 편리함에 중독될 겁니다. 마치 손에 익은 툴처럼요. 앞으로도 계속 발전할 SwiftUI 생태계에서 Combine은 여전히 강력한 도구로 남아있을 거예요.
