이전 강의자료에 Alamofire 라이브러리를 적용하여 간단하게 리팩토링 해보려고 한다.
AlamoFire는 Swift의 HTTPS 라이브러리로, URLSession 을 한단계 감싸서 네트워크 코드 사용성에 편의를 제공한다.
https://github.com/Alamofire/Alamofire.git
을 붙여넣어 Package Dependency를 설정한 후 시작.
// 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()
}
// 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)
}
}
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
}
}
}
}
}
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("데이터 로드 실패")
}
}
}
// 서버에서 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()
}
}
}
// 서버에서 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("데이터 로드 실패")
}
}
}