티스토리 뷰
개발을 하다 보면 이런 고민을 한 번쯤 해봤을 거다.
- "이 값이 변경될 때마다 UI를 자동으로 업데이트하고 싶어!"
- "사용자가 입력을 계속 바꾸는데, 그 값들을 실시간으로 반영하는 게 너무 번거로워!"
우리는 보통 이런 문제를 해결할 때 이벤트 리스너를 설정하고, 변경될 때마다 수동으로 UI를 업데이트하곤 한다.
하지만 이런 방식은 점점 코드가 꼬이고, 유지보수가 힘들어지는 문제를 만든다.
이럴 때 반응형 프로그래밍(Responsive Programming, RP)이 등장한다.
RP는 데이터의 흐름을 선언적으로 정의하고, 데이터가 바뀔 때마다 자동으로 반응하도록 만드는 패러다임이다.
반응형 프로그래밍이 뭔데?
간단하게 말하면, 데이터의 변화를 감지하고, 이에 따라 자동으로 반응하는 방식이다.
기본적으로 이벤트 기반 프로그래밍과 비슷하지만, 더 높은 수준에서 데이터 흐름을 관리한다.
이걸 이해하려면, 먼저 전통적인 프로그래밍 방식과 비교해보는 게 좋다.
전통적인 이벤트 기반 방식 (Imperative)
var number = 0
func updateUI() {
print("UI 업데이트: \(number)")
}
// 사용자가 값을 변경할 때마다 직접 호출해야 함
number = 5
updateUI() // "UI 업데이트: 5"
number = 10
updateUI() // "UI 업데이트: 10"
위 코드를 보면, number 값이 바뀔 때마다 updateUI()를 직접 호출해야 한다.
만약 UI 요소가 여러 개라면? 매번 직접 업데이트를 해야 하니 점점 코드가 복잡해진다.
반응형 프로그래밍 방식 (Declarative)
import Combine
let number = CurrentValueSubject<Int, Never>(0)
let subscription = number.sink { newValue in
print("UI 업데이트: \(newValue)")
}
number.send(5) // "UI 업데이트: 5"
number.send(10) // "UI 업데이트: 10"
여기서 number.send(값)을 호출하면, 자동으로 UI가 업데이트된다!
우리는 그냥 "이 값이 변할 때마다 실행될 로직"을 한 번 선언해두면 끝이다.
이게 바로 반응형 프로그래밍의 핵심 철학이다.
RP의 핵심 개념
반응형 프로그래밍을 이해하려면, 몇 가지 핵심 개념을 알아야 한다.
1) 스트림(Stream)
- 데이터를 연속적인 흐름(스트림)으로 다룬다.
- 예를 들어, 사용자의 입력, API 응답, 센서 데이터 등은 모두 스트림으로 볼 수 있다.
2) 옵저버블(Observable)
- 데이터를 구독(Subscribe)할 수 있도록 만든 객체
- Swift에서는 Combine의 Publisher(발행자)가 이 역할을 한다.
- 값이 변할 때마다 자동으로 구독자(Subscriber)들에게 알림을 보낸다.
3) 연산자(Operators)
- 데이터를 변환하거나 필터링하는 기능.
- 예를 들어, map, filter, combineLatest 같은 연산자를 사용하면 데이터 흐름을 조작할 수 있다.
3. 반응형 프로그래밍을 적용해보자!
이제 실제로 반응형 프로그래밍을 활용하는 예제를 보자.
Swift에서는 Combine을 사용해서 RP를 구현할 수 있다.
(1) 버튼 클릭 이벤트를 반응형으로 처리하기
보통 버튼 클릭을 처리할 때 IBAction을 써서 직접 구현한다.
하지만 Combine을 사용하면 이렇게 바꿀 수 있다.
import Combine
let buttonTap = PassthroughSubject<Void, Never>()
let subscription = buttonTap.sink {
print("버튼이 클릭됨!")
}
// 버튼이 클릭될 때마다 send() 호출
buttonTap.send() // "버튼이 클릭됨!"
buttonTap.send() // "버튼이 클릭됨!"
PassthroughSubject는 일종의 이벤트 스트림이다.
버튼이 클릭될 때마다 .send()를 호출하면 자동으로 반응한다.
(2) 텍스트 입력을 실시간으로 감지하기
사용자가 입력할 때마다 UI를 업데이트하고 싶다면?
import Combine
let textFieldInput = CurrentValueSubject<String, Never>("")
let subscription = textFieldInput
.sink { newText in
print("사용자가 입력한 값: \(newText)")
}
textFieldInput.send("안녕하세요") // "사용자가 입력한 값: 안녕하세요"
textFieldInput.send("Swift") // "사용자가 입력한 값: Swift"
즉, 사용자가 입력할 때마다 UI가 자동으로 업데이트된다!
기존 방식처럼 textFieldDidChange() 같은 함수를 매번 구현할 필요가 없다.
(3) API 요청을 반응형으로 처리하기
네트워크 요청을 하면, 응답이 올 때까지 기다려야 한다.
이걸 Combine으로 처리하면 깔끔하게 구현할 수 있다.
import Combine
struct APIClient {
static func fetchData() -> AnyPublisher<String, Error> {
Just("서버 응답 데이터")
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
}
let subscription = APIClient.fetchData()
.sink(receiveCompletion: { completion in
print("요청 완료: \(completion)")
}, receiveValue: { data in
print("서버에서 받은 데이터: \(data)")
})
// "서버에서 받은 데이터: 서버 응답 데이터"
// "요청 완료: finished"
이렇게 하면 네트워크 응답이 올 때까지 기다렸다가 자동으로 UI를 업데이트할 수 있다.
기존의 completionHandler 방식보다 훨씬 직관적이고 깔끔하다.
RP는 어디서 유용할까?
그럼 반응형 프로그래밍이 실제 개발에서 어떻게 유용할까?
✅ 사용자 입력을 실시간으로 감지해야 할 때
- 검색 필터, 자동완성 기능
✅ 네트워크 요청을 쉽게 관리하고 싶을 때
- API 요청을 하고, 응답이 오면 UI를 자동으로 업데이트
✅ 여러 개의 데이터를 조합해야 할 때
- combineLatest() 같은 연산자를 사용해서 여러 개의 데이터를 합칠 수 있음
정리 – 반응형 프로그래밍이 왜 중요할까?
반응형 프로그래밍을 한마디로 정의하면 "데이터의 흐름을 선언적으로 다루는 방식" 이다.
- 기존 방식(OOP)은 데이터가 바뀔 때마다 직접 업데이트를 해야 했지만,
- RP에서는 데이터의 변화를 감지하고, 이에 자동으로 반응할 수 있다.
Swift에서는 RxSwift 와 Combine을 활용하면 쉽게 RP 스타일로 개발할 수 있다.
특히 UI와 데이터가 밀접하게 연관된 iOS 개발에서는 RP를 잘 활용하면 코드가 훨씬 깔끔해지고 유지보수도 쉬워진다!
사실 처음엔 나도 이게 왜 필요할까 싶었는데, 지금은 이걸 안 쓰면 오히려 불편할 정도로 편리하다...
이제는 비동기 코드를 볼 때마다 "이걸 그냥 스트림으로 바꾸면 더 깔끔할 텐데…" 하는 생각이 많이 든다.
RP에 대해 더 깊이 다루면 좋을 주제가 있을까?
아니면 실무에서 어떻게 깔끔하게 적용하면 좋을지 고민 해봐야겠다.
'프로그래밍 공부' 카테고리의 다른 글
| ARC(Automatic Reference Counting) – Swift는 어떻게 메모리를 관리할까? (1) | 2025.02.20 |
|---|---|
| Swift – iOS 개발자가 알아야 할 모든 것 (2) | 2025.02.19 |
| 객체 지향 설계의 SOLID 원칙 – 제대로 알고 써보자! (0) | 2025.02.18 |
| 함수형 프로그래밍(FP) – 진짜 왜 써야 할까? (0) | 2025.02.17 |
| 프로토콜 지향 프로그래밍 (POP, Protocol-Oriented Programming) (0) | 2025.02.13 |