[Swift/iOS] Alamofire 사용해보기

팔랑이·2024년 7월 17일
0

iOS/Swift

목록 보기
50/71
post-thumbnail

이전 강의자료에 Alamofire 라이브러리를 적용하여 간단하게 리팩토링 해보려고 한다.

AlamoFire는 Swift의 HTTPS 라이브러리로, URLSession 을 한단계 감싸서 네트워크 코드 사용성에 편의를 제공한다.

https://github.com/Alamofire/Alamofire.git
을 붙여넣어 Package Dependency를 설정한 후 시작.


1. 서버 데이터 불러오는 일반적인 메서드

기존 코드:

// Decodable을 채택하는 어떤 타입도 T? 안에 들어갈 수 있다
// 가져와야 할 API가 2가지인데, 어느 타입에서라도 일반적으로 재사용할 수 있게 제네릭을 사용함
// escaping 클로저: 메서드가 끝이 나더라도 탈출해서 돌아다니다 언제든지 실행될 수 있다는 뜻
private func fetchData<T: Decodable>(url: URL, completion: @escaping (T?) -> Void) {
    let session = URLSession(configuration: .default)
    session.dataTask(with: URLRequest(url: url)) { data, response, error in
        guard let data, error == nil else {
            print("데이터 로드 실패")
            completion(nil)
            return
        }
        // http status 코드 성공 범위는 200번대
        // HTTPURLResponse 안에 http status code를 깔 수 있기 때문에 타입 캐스팅
        let successRange = 200..<300
        if let response = response as? HTTPURLResponse, successRange.contains(response.statusCode) {
            guard let decodedData = try? JSONDecoder().decode(T.self, from: data) else {
                print("Json 디코딩 실패")
                completion(nil)
                return
            }
            completion(decodedData)
        } else {
            print("응답 오류")
            completion(nil)
        }
    }.resume()
}

Alamofire 코드:

// status code 지정 필요 X
// 성공 시 T, 실패 시 AFError
// responseDecodable 안에 decode 코드가 축약되어 들어가 있음
private func fetchDataByAlamofire<T: Decodable>(url: URL, completion: @escaping (Result<T, AFError>) -> Void) {
    // AF.request(url)로 네트워크 요청을 보냄
    AF.request(url).responseDecodable(of: T.self) { response in
        // response.result에 결과가 포함되어 있음
        completion(response.result)
    }
}

2. 현재 날씨 데이터를 받아오는 메서드

기존 코드:

private func fetchCurrentWeatherData() {
    var urlComponents = URLComponents(string: "https://api.openweathermap.org/data/2.5/weather")
    urlComponents?.queryItems = self.urlQueryItems
    
    guard let url = urlComponents?.url else {
        print("잘못된 URL")
        return
    }
    
    // 이렇게 타입 명시해주면 위의 T들이 CurrentWeatherResult로 인식
    // 강한 참조 순환 방지 위해 weak self 사용
    // 서버에서 불러오는 데이터는 백그라운드 스레드에서 처리 - UI를 그리는 작업은 메인스레드에서 처리, 따라서 서버 데이터는 백그라운드스레드
    fetchData(url: url) { [weak self] (result: CurrentWeatherResult?) in
        guard let self, let result else { return }

        // 현재 백그라운드스레드에서 작업중, 하지만 UI는 반드시 메인스레드에서 작업되어야 하기에 이를 명시해줌
        DispatchQueue.main.async {
            self.tempLabel.text = "\(Int(result.main.temp))°C"
            self.tempMinLabel.text = "최저: \(Int(result.main.tempMin))°C"
            self.tempMaxLabel.text = "최고: \(Int(result.main.tempMax))°C"
        }

        guard let imageUrl = URL(string: "https://openweathermap.org/img/wn/\(result.weather[0].icon)@2x.png") else {
            return
        }

        // image 로드 작업도 백그라운드스레드 작업, 따라서 UI작업 main스레드 작업 명시
        if let data = try? Data(contentsOf: imageUrl) {
            if let image = UIImage(data: data) {
                DispatchQueue.main.async {
                    self.imageView.image = image
                }
            }
        }
    }
}

Alamofire 코드:

private func fetchCurrentWeatherData() {
    var urlComponents = URLComponents(string: "https://api.openweathermap.org/data/2.5/weather")
    urlComponents?.queryItems = self.urlQueryItems
    
    guard let url = urlComponents?.url else {
        print("잘못된 URL")
        return
    }

    fetchDataByAlamofire(url: url) { [weak self] (result: Result<CurrentWeatherResult, AFError>) in
        guard let self else { return }
        switch result {
        case .success(let result):
            // 네트워크 작업이 완료되면 UI 작업을 메인스레드에서 처리
            DispatchQueue.main.async {
                self.tempLabel.text = "\(Int(result.main.temp))°C"
                self.tempMinLabel.text = "최저: \(Int(result.main.tempMin))°C"
                self.tempMaxLabel.text = "최고: \(Int(result.main.tempMax))°C"
            }
            
            guard let imageUrl = URL(string: "https://openweathermap.org/img/wn/\(result.weather[0].icon)@2x.png") else {
                return
            }
            
            // Alamofire 사용한 이미지 로드
            AF.request(imageUrl).responseData { response in
                if let data = response.data, let image = UIImage(data: data) {
                    // UI 작업을 메인스레드에서 처리
                    DispatchQueue.main.async {
                        self.imageView.image = image
                    }
                }
            }
        case .failure(let error):
            print("데이터 로드 실패")
        }
    }
}

3. 예상 날씨 데이터 받아오는 메서드

기존 코드:

// 서버에서 5일간 날씨 예보 데이터를 불러오는 메서드
private func fetchForecastData() {
    var urlComponents = URLComponents(string: "https://api.openweathermap.org/data/2.5/forecast")
    urlComponents?.queryItems = self.urlQueryItems
    
    guard let url = urlComponents?.url else {
        print("잘못된 URL")
        return
    }

    fetchData(url: url) { [weak self] (result: ForecastWeatherResult?) in
        guard let self, let result else { return }
        // 콘솔에 데이터 잘 불러왔는지 찍어보기
        for forecastWeather in result.list {
            print("\(forecastWeather.main) \(forecastWeather.dtTxt)")
        }

        DispatchQueue.main.async {
            self.dataSource = result.list
            self.tableView.reloadData()
        }
    }
}

Alamofire 코드:

// 서버에서 5일간 날씨 예보 데이터를 불러오는 메서드
private func fetchForecastData() {
    var urlComponents = URLComponents(string: "https://api.openweathermap.org/data/2.5/forecast")
    urlComponents?.queryItems = self.urlQueryItems
    
    guard let url = urlComponents?.url else {
        print("잘못된 URL")
        return
    }

    fetchDataByAlamofire(url: url) { [weak self] (result: Result<ForecastWeatherResult, AFError>) in
        guard let self else { return }
        switch result {
        case .success(let result):
            // 네트워크 작업이 완료되면 UI 작업을 메인스레드에서 처리
            DispatchQueue.main.async {
                self.dataSource = result.list
                self.tableView.reloadData()
            }
        case .failure(let error):
            print("데이터 로드 실패")
        }
    }
}
profile
정체되지 않는 성장

0개의 댓글