오늘은 검색창에서 주소를 입력하면 검색 결과로 연관 주소가 나오게 하는 기능을 구현했다.
Kakao 맵 API를 사용해서 기능을 구현했다.

searchBar.rx.text.orEmpty
.debounce(.milliseconds(300), scheduler: MainScheduler.instance) // 사용자가 입력을 멈춘 뒤 300ms 후에 다음 작업 실행
.distinctUntilChanged() // 이전 텍스트와 같으면 무시
.filter { !$0.isEmpty } // 빈 문자열은 API 요청하지 않도록 필터링
// 텍스트가 입력될 때마다 fetchAddress() 호출
// flatMapLatest는 사용자가 입력을 계속 바꿀 때, 이전 네트워크 요청을 무시하고 가장 마지막 것만 유지합니다.
.flatMapLatest { [weak self] query in
self?.viewModel.fetchAddress(query: query) ?? .just([]) // nil 이면, 빈 배열을 하나 방출하고 끝나는 Observable을 대신 반환합니다.
}
.observe(on: MainScheduler.instance)
.subscribe(onNext: { [weak self] documents in
let roadAddresses = documents.compactMap { $0.address } // Kakao API 응답에서 .address만 추출해 배열로 만들고
self?.dataRelay.accept(roadAddresses) // dataRelay에 저장 → 테이블 뷰가 자동 업데이트됨
}, onError: { error in
print("검색 에러 발생: \(error)")
}).disposed(by: disposeBag)
bindSearchBar()searchBar.rx.text.orEmpty
RxCocoa의 UISearchBar 텍스트 스트림을 옵저빙합니다.
debounce(300ms)
distinctUntilChanged()
filter { !$0.isEmpty }
flatMapLatest → ViewModel 호출
fetchAddress() 호출결과 처리
let roadAddresses = documents.compactMap { $0.address }
self?.dataRelay.accept(roadAddresses)
.address만 추출해 배열로 만들고dataRelay에 저장 → 테이블 뷰가 자동 업데이트됨dataRelay.bind(to: tableView.rx.items(...))dataRelay
.bind(to: tableView.rx.items(
cellIdentifier: ...,
cellType: ...)
) { row, address, cell in
cell.configure(data: address)
}
Address 데이터를 받아서 셀에 설정SearchResultCell의 configure(data:)에서 UI를 구성함| 개념 | 설명 |
|---|---|
BehaviorRelay<[Address]> | 테이블에 표시할 데이터 상태 저장 |
.flatMapLatest | 사용자가 검색 중일 때 이전 요청 취소하고 새 요청만 수행 |
.bind(to: tableView.rx.items) | Rx 방식으로 테이블 뷰를 구성함. reloadData() 불필요 |
.debounce() | API 요청 남발 방지 (입력 멈췄을 때만 호출) |
사용자가 주공로 입력 →
0.3초 후 ViewModel API 요청 →
검색 결과의 .address들을 추출 →
dataRelay에 저장 →
RxCocoa가 테이블 뷰 자동 업데이트 →
셀에 주소 표시
searchBar.rx.text.orEmpty의 의미searchBar.rx.text는 Observable<String?> 타입이다..orEmpty를 붙이면 Observable<String>로 바뀌며 nil을 자동으로 빈 문자열로 대체한다.map { $0 ?? "" }와 같은 효과..subscribe(onNext:)의 의미Observable을 구독하여, 값이 방출될 때(onNext) 해당 클로저가 실행된다.searchBar.rx.text.orEmpty.subscribe(onNext: { text in ... }).bind(to:) vs .accept()| 항목 | .bind(to:) | .accept() |
|---|---|---|
| 목적 | Observable을 다른 대상(UI, Relay 등)에 연결 | Relay에 수동으로 값을 밀어넣음 |
| 사용 위치 | ViewController 등에서 UI 바인딩 | ViewModel 내부에서 상태 갱신 |
| 예시 | observable.bind(to: label.rx.text) | relay.accept("새 값") |
.asObservable()의 의미Relay, Subject 등 쓰기 가능한 소스를 읽기 전용으로 노출할 때 사용.
ViewModel에서 내부 상태는 Relay, 외부엔 Observable로만 제공하는 패턴에 유용.
예시:
private let itemsRelay = BehaviorRelay<[String]>(value: [])
var items: Observable<[String]> { itemsRelay.asObservable() }
flatMapLatest가 필요한 이유"강" → "강남" → "강남구" 입력 시 "강남구" 요청만 처리..just([])의 의미Observable.just([])는 한 번 빈 배열을 방출하고 종료되는 스트림이다.?? .just([])는 옵셔널이 nil일 경우 빈 배열을 대신 반환해주기 위한 안전 처리.오늘은 RxSwift + RxCocoa를 이용한 검색 기능 구현 중 다음 개념들을 학습하고 적용함
.orEmpty)subscribe, bind(to:), accept() 차이점flatMapLatest의 사용 목적과 대체 방식 (searchButtonClicked).asObservable() 사용