| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- Combine개념
- CURSOR
- priority
- Swift
- 코딩에이전트
- RxSwift에서Combine
- replaceNil
- SwiftCombine
- Publisher
- Combine공부
- iOS기술블로그
- AI개발도구추천
- compactMap
- ChatGPTCodex
- RxSwift비교
- 비동기
- PassthroughSubject
- Codex리뷰
- 라이브러리
- RxSwift
- CurrentValueSubject
- Codex사용법
- autoLayout
- Combine
- tryMap
- ios
- User Rules
- Cocoapods
- Combine입문
- hugging
- Today
- Total
Tong's Blog
[Swift] Combine Transforming Operator 정리 [map, tryMap, compactMap, flatMap, scan, replaceNil] + RxSwift 비교 본문
[Swift] Combine Transforming Operator 정리 [map, tryMap, compactMap, flatMap, scan, replaceNil] + RxSwift 비교
통스 2026. 1. 6. 00:03안녕하세요.
지난 글에서는 Combine Publisher 종류를 정리하고, 각각 RxSwift의 어떤 컴포넌트와 대응되는지 살펴봤습니다.
이번 3편에서는 실무에서 가장 많이 사용되는 Combine Operator 들을 정리하고, RxSwift의 동일 기능과 어떤 차이가 있는지 비교해보려고 합니다.
Operator란?
Publisher에서 전달되는 값들을 변환 / 필터링 / 조합 / 제어하는 중간 처리 단위입니다.
즉, 이벤트 흐름을 원하는 형태로 가공하여 Subscriber가 필요한 데이터만 받을 수 있도록 만들어주는 역할입니다.
이번 포스팅에서는 그중에서도 Transforming Operator 중심으로 설명과 비교를 해보겠습니다.
변환(Transforming) Operator란?
이름 그대로, '들어온 값을 다른 값으로 바꿔서 흘려보내는 역할'을 하는 Operator 입니다.
- 값 자체를 바꾸는 경우: map, tryMap, compactMap
- 다른 Publisher로 바꾸는 경우: flatMap
- 이전 값과 누적해서 새로운 값을 만드는 경우: scan
- nil을 다른 값으로 치환하는 경우: replaceNil
Publisher
.map { ... } // 값 변환
.flatMap { ... } // 비동기 변환
.scan(…) // 누적 계산
.sink { ... } // 최종 소비
이제 하나씩, Combine 코드 → RxSwift 비교 순으로 보겠습니다.
1. map - element 값 변환
https://developer.apple.com/documentation/combine/publisher/map(_:)-99evh
map(_:) | Apple Developer Documentation
Transforms all elements from the upstream publisher with a provided closure.
developer.apple.com
들어온 값을 다른 값으로 '1:1 매핑' 하는 가장 기본적인 연산자입니다.
Combine 예제
[1, 2, 3].publisher
.map { $0 * 10 }
.sink { value in
print("값: \(value)")
}
.store(in: &bag)
// 값: 10
// 값: 20
// 값: 30
RxSwift
Observable.from([1, 2, 3])
.map { $0 * 10 }
.subscribe(onNext: { value in
print("값: \(value)")
})
.disposed(by: disposeBag)
2. tryMap - 에러를 던질 수 있는 map
map 내부에서 throw 를 하고 싶을 때 사용하는 버전입니다.
에러 타입이 Never → Error 로 바뀝니다.
Combine 예제
["1", "2", "a", "4"].publisher
.tryMap { value -> Int in
guard let intValue = Int(value) else {
throw ParseError()
}
return intValue
}
.sink(
receiveCompletion: { print("완료: \($0)") },
receiveValue: { print("값: \($0)") }
)
.store(in: &bag)
// 값: 1
// 값: 2
// 완료: failure(CombineRxSwift.ViewController.ParseError())
RxSwift
RxSwift에서는 map 클로저 안에서 바로 throw 가 가능합니다.
Observable의 에러 타입이 항상 Error 이기 때문에 별도 tryMap 이 필요 없습니다.
Observable.from(["1", "2", "a", "4"])
.map { value -> Int in
guard let intValue = Int(value) else {
throw ParseError()
}
return intValue
}
.subscribe(
onNext: { print("값: \($0)") },
onError: { print("에러: \($0)") }
)
.disposed(by: disposeBag)
3. compactMap – nil 제거하면서 변환
변환 결과가 Optional 일 수 있을 때 nil 은 버리고, Optional 의 wrapped 값만 흐르게 하는 연산자
Combine
["1", "2", "a", "4"].publisher
.compactMap { Int($0) }
.sink { value in
print("값: \(value)")
}
.store(in: &bag)
// 출력: 1, 2, 4 ('a'는 Int로 변환 실패 → nil → 필터링)
RxSwift
Observable.from(["1", "2", "a", "4"])
.compactMap { Int($0) }
.subscribe(onNext: { value in
print("값: \(value)")
})
.disposed(by: disposeBag)
4. flatMap – 비동기 스트림을 평탄화
map 은 '값 → 값' 변환이라면,
flatMap 은 '값 → Publisher' 로 바꾸고, 그 Publisher의 결과를 다시 한 스트림으로 평탄화합니다.
Combine
func fetchUser(id: Int) -> AnyPublisher<String, Never> {
Just("User_\(id)")
.delay(for: .milliseconds(100), scheduler: RunLoop.main)
.eraseToAnyPublisher()
}
[1, 2, 3].publisher
.flatMap { id in
fetchUser(id: id)
}
.sink { value in
print("결과:", value)
}
.store(in: &bag)
// 출력: User_1, User_2, User_3 (비동기 순서에 따라 달라질 수 있음)
RxSwift
func fetchUser(id: Int) -> Observable<String> {
Observable.just("User_\(id)")
.delay(.milliseconds(100), scheduler: MainScheduler.instance)
}
Observable.from([1, 2, 3])
.flatMap { id in
fetchUser(id: id)
}
.subscribe(onNext: { value in
print("결과:", value)
})
.disposed(by: disposeBag)
5. scan – 이전 값을 누적하면서 새로운 값 생성
'누적 합' 같은 상황에서 자주 쓰입니다.
reduce 가 '끝나고 한 번만 결과를 내보내는' 것이라면,
scan 은 매 단계마다 중간 누적값을 모두 흘려보냅니다.
Combine
[1, 2, 3, 4].publisher
.scan(0) { accumulated, newValue in
accumulated + newValue
}
.sink { value in
print("누적값:", value)
}
.store(in: &bag)
// 출력: 1, 3, 6, 10
RxSwift
Observable.from([1, 2, 3, 4])
.scan(0) { accumulated, newValue in
accumulated + newValue
}
.subscribe(onNext: { value in
print("누적값:", value)
})
.disposed(by: disposeBag)
6. replaceNil – nil을 기본값으로 치환
Combine
let values: [Int?] = [1, nil, 3]
values.publisher
.replaceNil(with: 0)
.sink { value in
print("값:", value)
}
.store(in: &bag)
// 값: Optional(1)
// 값: Optional(0)
// 값: Optional(3)
RxSwift
RxSwift에는 replaceNil 이라는 이름의 기본 연산자는 없고, 대신 아래처럼 조합해서 만듭니다.
Observable.from([1, nil, 3] as [Int?])
.map { $0 ?? 0 }
.subscribe(onNext: { value in
print("값:", value)
})
.disposed(by: disposeBag)
// 값: 1
// 값: 0
// 값: 3
7. 한 번에 비교
| 기능 | Combine | RxSwift | 설명 |
| 단순 값 변환 | map | map | 가장 기본적인 값 변환 |
| 에러 던지는 변환 | tryMap | map + throw | Combine은 에러 타입이 Never가 아닐 수 있음 |
| Optional 제거 변환 | compactMap | compactMap | nil 제거 후 wrapped 값만 전달 |
| Publisher 평탄화 | flatMap | flatMap | 값 → Publisher → 다시 단일 스트림 |
| 누적값 생성 | scan | scan | 단계별 누적 결과 스트림 |
| nil 치환 | replaceNil | map { $0 ?? default } | Combine 전용 편의 연산자 |
네트워크 사용 예시
Combine
struct SearchResponse: Decodable {
let items: [Item]
}
struct Item: Decodable {
let title: String
}
func search(query: String) -> AnyPublisher<[String], Error> {
api.search(query: query) // AnyPublisher<Data, Error>
.decode(type: SearchResponse.self, decoder: JSONDecoder())
.map { $0.items } // [Item]
.map { items in items.map { $0.title } } // [String]
.eraseToAnyPublisher()
}
RxSwift
func search(query: String) -> Observable<[String]> {
api.search(query: query) // Observable<Data>
.map { data -> SearchResponse in
try JSONDecoder().decode(SearchResponse.self, from: data)
}
.map { $0.items.map { $0.title } }
}
이번 3편에서는 Combine의 변환 Operator 들을 중심으로 살펴봤습니다.
그리고 각각이 RxSwift에서는 어떤 연산자로 대응되는지도 함께 비교해봤습니다.
읽어주셔서 감사합니다.
'iOS > Combine' 카테고리의 다른 글
| [Swift] Combine Publisher 종류 정리 + RxSwift와 비교해보기 (2편) (0) | 2025.11.20 |
|---|---|
| [Swift] Combine 개념과 RxSwift 와 비교해보기 (0) | 2025.11.12 |