[내일배움캠프 57일차] 날씨 앱 팀 프로젝트 3

NH·2025년 5월 22일

내일배움캠프

목록 보기
57/62
post-thumbnail

팀 프로젝트: 봄여어름갈겨어울

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

완성 모습


개발한 코드!

  • 검색창에 지명을 입력하면 Rx로 반응해서 결과를 보여주는 기능
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)

1. bindSearchBar()

searchBar.rx.text.orEmpty

RxCocoa의 UISearchBar 텍스트 스트림을 옵저빙합니다.

처리 흐름

  1. debounce(300ms)

    • 사용자가 입력을 멈춘 뒤 300ms 후에 다음 작업 실행
  2. distinctUntilChanged()

    • 이전 텍스트와 같으면 무시
  3. filter { !$0.isEmpty }

    • 빈 문자열은 API 요청하지 않도록 필터링
  4. flatMapLatest → ViewModel 호출

    • 텍스트가 입력될 때마다 fetchAddress() 호출
  5. 결과 처리

    let roadAddresses = documents.compactMap { $0.address }
    self?.dataRelay.accept(roadAddresses)
    • Kakao API 응답에서 .address만 추출해 배열로 만들고
    • dataRelay에 저장 → 테이블 뷰가 자동 업데이트됨

2. dataRelay.bind(to: tableView.rx.items(...))

dataRelay
    .bind(to: tableView.rx.items(
        cellIdentifier: ...,
        cellType: ...)
    ) { row, address, cell in
        cell.configure(data: address)
    }
  • Rx 방식으로 테이블 뷰를 구성
  • Address 데이터를 받아서 셀에 설정
  • SearchResultCellconfigure(data:)에서 UI를 구성함

요약

개념설명
BehaviorRelay<[Address]>테이블에 표시할 데이터 상태 저장
.flatMapLatest사용자가 검색 중일 때 이전 요청 취소하고 새 요청만 수행
.bind(to: tableView.rx.items)Rx 방식으로 테이블 뷰를 구성함. reloadData() 불필요
.debounce()API 요청 남발 방지 (입력 멈췄을 때만 호출)

흐름

사용자가 주공로 입력 →
0.3초 후 ViewModel API 요청 →
검색 결과의 .address들을 추출 →
dataRelay에 저장 →
RxCocoa가 테이블 뷰 자동 업데이트 →
셀에 주소 표시


1. searchBar.rx.text.orEmpty의 의미

  • searchBar.rx.textObservable<String?> 타입이다.
  • .orEmpty를 붙이면 Observable<String>로 바뀌며 nil을 자동으로 빈 문자열로 대체한다.
  • 즉, map { $0 ?? "" }와 같은 효과.

2. .subscribe(onNext:)의 의미

  • Observable을 구독하여, 값이 방출될 때(onNext) 해당 클로저가 실행된다.
  • 예: searchBar.rx.text.orEmpty.subscribe(onNext: { text in ... })

3. .bind(to:) vs .accept()

항목.bind(to:).accept()
목적Observable을 다른 대상(UI, Relay 등)에 연결Relay에 수동으로 값을 밀어넣음
사용 위치ViewController 등에서 UI 바인딩ViewModel 내부에서 상태 갱신
예시observable.bind(to: label.rx.text)relay.accept("새 값")

4. .asObservable()의 의미

  • Relay, Subject 등 쓰기 가능한 소스를 읽기 전용으로 노출할 때 사용.

  • ViewModel에서 내부 상태는 Relay, 외부엔 Observable로만 제공하는 패턴에 유용.

  • 예시:

    private let itemsRelay = BehaviorRelay<[String]>(value: [])
    var items: Observable<[String]> { itemsRelay.asObservable() }

5. flatMapLatest가 필요한 이유

  • 입력이 짧은 시간에 여러 번 바뀔 때, 마지막 요청만 처리하고 이전 것은 취소.
  • 검색과 같이 빠르게 입력이 바뀌는 상황에 적합.
  • 예: "강""강남""강남구" 입력 시 "강남구" 요청만 처리.

6. .just([])의 의미

  • Observable.just([])는 한 번 빈 배열을 방출하고 종료되는 스트림이다.
  • ?? .just([])는 옵셔널이 nil일 경우 빈 배열을 대신 반환해주기 위한 안전 처리.

요약

오늘은 RxSwift + RxCocoa를 이용한 검색 기능 구현 중 다음 개념들을 학습하고 적용함

  • 옵셔널 스트림 처리 (.orEmpty)
  • subscribe, bind(to:), accept() 차이점
  • flatMapLatest의 사용 목적과 대체 방식 (searchButtonClicked)
  • 단방향 데이터 흐름을 위한 .asObservable() 사용
  • 실전 UI와 비즈니스 로직을 나누는 MVVM 패턴의 핵심 개념 연습
profile
iOS 개발 블로그

0개의 댓글