[250417] 날씨 앱 만들기

ON&OFF·2025년 4월 17일

iOS TIL

목록 보기
15/18

📖 오늘 진행한 것

URLSession와 API를 활용해 날씨 데이터를 받아와 보여주는 앱의 주요 기능들을 구현해보았다.

❓ 겪은 문제점/의문점

로컬 환경에서 데이터를 스스로 넣고, 그걸 사용하는 단계에서 외부에서 데이터를 받아오는 단계가 되다 보니까 해야 할 작업도 늘어나고, 코드도 어려워져서 헷갈리는 부분이 많았다..

    // 서버 데이터를 불러오는 메서드
    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 = data, error == nil else {
                print("데이터 로드 실패")
                completion(nil)
                return
            }
            // 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()
    }   
    // 서버에서 현재 날씨 데이터를 불러오는 메서드
    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
        }    
        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.temp_min))°C"
                self.tempMaxLabel.text = "최고: \(Int(result.main.temp_max))°C"
            }
            guard let imageUrl = URL(string: "https://openweathermap.org/img/wn/\(result.weather[0].icon)@2x.png") else { return }   
            // image 를 로드하는 작업은 백그라운드 쓰레드 작업
            if let data = try? Data(contentsOf: imageUrl) {
                if let image = UIImage(data: data) {
                    // 이미지뷰에 이미지를 그리는 작업은 UI 작업이기 때문에 다시 메인 쓰레드에서 작업.
                    DispatchQueue.main.async {
                        self.imageView.image = image
                    }
                }
            }
        }
    }
    // 서버에서 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 }          
            // result 콘솔에 찍어보기
            for forecastWeather in result.list {
                print("\(forecastWeather.main)\n\(forecastWeather.dtTxt)\n\n")
            }       
            // UI 작업은 메인 쓰레드에서
            DispatchQueue.main.async {
                self.dataSource = result.list
                self.tableView.reloadData()
            }
        }
    }

이게 주요 함수들인데.. 뭐가 뭔지 잘 몰라서 강의도 두번 돌려보고 따라 쳐보기도 두번 했지만 여전히 감이 잘 오진 않는다.
어떤 흐름인지는 대강 이해가 가는데 사용되는 메서드들이나 제너럴,옵셔널,타입캐스팅이 난무하는 코드가 날 어지럽게 만든다..

‼️ 해결 방법

더 많이 보고 코드를 하나씩 뜯어서 무슨 뜻인지 이해해 보자.. 🤯

💡 느낀 점

갈수록 새로운 것들이 나와 어려워지는 것 같지만 또 배우는 재미가 있어서 마냥 싫지만은 않다.

🎁 다음에 진행할 것

이 코드들 씹어먹기..

profile
안 되면 될 때까지

0개의 댓글