Alamofire와 Combine의 조합으로 API 요청하는 방식을 알아보자.
1. Alamofire + Combine의 조합이란?
- Alamofire: 네트워크 요청을 간편하게 해주는 라이브러리.
- Combine: 비동기 데이터를 스트림으로 처리하는 프레임워크.
이 둘을 조합하면 네트워크 요청을 더 깔끔하고 선언적으로 처리할 수 있다.
즉, 네트워크 요청 → 응답 처리 → UI 업데이트 흐름이 자연스럽게 이어진다.
2. 기본적인 Alamofire + Combine 패턴
Alamofire는 publishDecodable이라는 메서드를 제공해서 Combine과 쉽게 연동할 수 있다.
✅ Step 1. APIManager에서 Publisher 반환
// Combine으로 API 요청 (Publisher 반환)
func fetchCurrentWeather(lat: Double, lon: Double) -> AnyPublisher<CurrentWeatherResult, Error> {
guard let url = makeURL(endpoint: "weather", queryItems: [
URLQueryItem(name: "lat", value: "\(lat)"),
URLQueryItem(name: "lon", value: "\(lon)")
]) else {
return Fail(error: URLError(.badURL)).eraseToAnyPublisher()
}
return AF.request(url) // 1. 네트워크 요청
.publishDecodable(type: CurrentWeatherResult.self) // 2. JSON 응답을 CurrentWeatherResult로 디코딩 (Publisher 반환)
.value() // 3. 성공한 데이터 값만 추출
.mapError { $0 as Error } // 4. Alamofire의 에러를 Combine의 에러로 변환
.eraseToAnyPublisher() // 5. AnyPublisher로 타입을 추상화해서 반환
}
3. ViewModel에서 API 호출하고 데이터 바인딩
이제 WeatherAPIManager에서 Publisher로 데이터를 반환하니까, ViewModel에서 구독(Subscribe)하면 된다.
✅ Step 2. ViewModel에서 API 구독
// **UI에 바인딩할 데이터들**
@Published var currentWeather: CurrentWeatherResult?
@Published var errorMessage: String?
private var cancellables = Set<AnyCancellable>() // 구독 관리
// **API 호출**
func fetchWeather(lat: Double, lon: Double) {
WeatherAPIManager.shared.fetchCurrentWeather(lat: lat, lon: lon)
.receive(on: DispatchQueue.main) // 1. 메인 스레드에서 결과 받기 (UI 업데이트)
.sink(receiveCompletion: { [weak self] completion in // 2. 성공/실패 처리
switch completion {
case .finished:
break // 성공적으로 끝난 경우
case .failure(let error):
self?.errorMessage = "날씨 정보를 불러오는데 실패했습니다: \(error.localizedDescription)"
}
}, receiveValue: { [weak self] weatherData in // 데이터 처리
self?.currentWeather = weatherData // ViewModel에 데이터 저장 → UI 업데이트
})
.store(in: &cancellables) // 메모리 관리
}
4. ViewController에서 ViewModel 바인딩
이제 ViewController에서 ViewModel의 데이터를 구독해서 UI를 업데이트할 수 있다.
✅ Step 3. ViewController에서 바인딩
// ViewModel과 UI 바인딩
private func setupBindings() {
viewModel.$currentWeather
.compactMap { $0 } // nil 값 필터링
.sink { [weak self] weatherData in
self?.updateWeatherUI(with: weatherData)
}
.store(in: &cancellables)
viewModel.$errorMessage
.compactMap { $0 }
.sink { [weak self] error in
self?.showErrorAlert(message: error)
}
.store(in: &cancellables)
}
// UI 업데이트 메서드
private func updateWeatherUI(with data: CurrentWeatherResult) {
// 받아온 날씨 데이터를 UI에 표시
print("현재 온도: \(data.main.temp)°C")
}
5. Alamofire + Combine의 동작 흐름
- APIManager: Alamofire를 이용해 네트워크 요청 → Publisher로 결과 반환.
- ViewModel: APIManager의 Publisher를 구독 → 결과 데이터를 @Published 프로퍼티에 저장.
- ViewController: ViewModel의 @Published 프로퍼티를 구독 → 데이터 변경 시 자동으로 UI 업데이트.
☑️ 이 방식의 장점
- 코드가 깔끔하고 명확해짐: 네트워크 요청, 데이터 처리, UI 업데이트가 각자 책임을 갖고 분리
- 비동기 처리가 간편해짐: 복잡한 콜백 지옥 없이 데이터를 다룰 수 있다.
- 에러 처리가 체계적: Combine의 sink로 성공과 실패를 한 곳에서 관리할 수 있다.
☑️ 코드 템플릿
Alamofire + Combine 네트워크 요청 패턴
AF.request(url)
.publishDecodable(type: YourModel.self)
.value()
.mapError { $0 as Error }
.eraseToAnyPublisher()
ViewModel에서 Combine 구독 패턴
Publisher
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
break
case .failure(let error):
// 에러 처리
}
}, receiveValue: { data in
// 성공적으로 받은 데이터 처리
})
.store(in: &cancellables)
'Devlog👩🏻💻 > iOS' 카테고리의 다른 글
[iOS] CoreLocation 사용하기 (0) | 2025.02.14 |
---|---|
[iOS] Combine: @Published, sink, assign (0) | 2025.02.11 |
[iOS] CollectionView 페이징 (Pagination), LoadMore (0) | 2024.06.29 |
[iOS] 스냅킷 updateLayoutConstraints 크래시 (equalToSuperview) (2) | 2024.06.19 |
[Error/Xcode] 시뮬레이터 SearchBar & TextField 키보드 오류 (4) | 2024.05.07 |