-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
날씨앱 [STEP 1] kun #15
base: rft_1_kun11
Are you sure you want to change the base?
날씨앱 [STEP 1] kun #15
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// | ||
// TempUnit.swift | ||
// WeatherForecast | ||
// | ||
// Created by MIN SEONG KIM on 2024/02/02. | ||
// | ||
|
||
import Foundation | ||
|
||
// MARK: - Temperature Unit | ||
enum TempUnit: String { | ||
case metric | ||
case imperial | ||
|
||
var expression: String { | ||
switch self { | ||
case .metric: return "℃" | ||
case .imperial: return "℉" | ||
} | ||
} | ||
|
||
var strOpposite: String { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 변수명만 보고 이 변수가 어떤 값을 반환하는지 추측하기 어려워보여요 |
||
switch self { | ||
case .metric: | ||
return "화씨" | ||
case .imperial: | ||
return "섭씨" | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// | ||
// WeatherInfo.swift | ||
// WeatherForecast | ||
// | ||
// Created by MIN SEONG KIM on 2024/02/05. | ||
// | ||
|
||
import Foundation | ||
|
||
struct WeatherDetailInfo { | ||
private let weatherForecastInfo: WeatherForecastInfo | ||
private let cityInfo: City | ||
private let tempUnit: TempUnit | ||
|
||
var date: String { weatherForecastInfo.date } | ||
|
||
var sunrise: String { cityInfo.sunriseString } | ||
var sunset: String { cityInfo.sunsetString } | ||
|
||
var weatherMain: String { weatherForecastInfo.weatherMain } | ||
var description: String { weatherForecastInfo.description } | ||
|
||
var temp: String { "\(weatherForecastInfo.temp)\(tempUnit.expression)"} | ||
var tempMax: String { "\(weatherForecastInfo.tempMax)\(tempUnit.expression)" } | ||
var tempMin: String { "\(weatherForecastInfo.tempMin)\(tempUnit.expression)" } | ||
|
||
var feelsLike: String { "\(weatherForecastInfo.feelsLike)\(tempUnit.expression)" } | ||
var pop: String { weatherForecastInfo.pop } | ||
|
||
var humidity: String { weatherForecastInfo.humidity } | ||
|
||
var iconName: String { weatherForecastInfo.iconName } | ||
Comment on lines
+15
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 computed property들을 만드신 이유가 궁금해요. Clean-Architecture에서는 서버에서 받은 모델을 뷰에서 사용할 모델로 새로 만드는 역할이 있는데요, DTO, Entity키워드로 검색해보시면 좋을 거 같고, 해당 레이어가 빠진다고 하더라도, weatherForecastInfo를 internal로 열어서 직접 접근하는게, computed property를 사용하는것보다 오버헤드가 적을거 같다는 생각이 드네요 |
||
|
||
//MARK: - Init | ||
init(weatherForecastInfo: WeatherForecastInfo, cityInfo: City, tempUnit: TempUnit) { | ||
self.weatherForecastInfo = weatherForecastInfo | ||
self.cityInfo = cityInfo | ||
self.tempUnit = tempUnit | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// | ||
// WeatherForecast - Weather.swift | ||
// Created by yagom. | ||
// Copyright © yagom. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
// MARK: - Weather JSON Format | ||
struct WeatherJSON: Decodable { | ||
let weatherForecast: [WeatherForecastInfo] | ||
let city: City | ||
} | ||
|
||
// MARK: - List | ||
struct WeatherForecastInfo: Decodable { | ||
let dt: TimeInterval | ||
let main: MainInfo | ||
let weather: Weather | ||
let dtTxt: String | ||
|
||
var date: String { | ||
let formatter: DateFormatter = DateFormatter() | ||
formatter.locale = .init(identifier: "ko_KR") | ||
formatter.dateFormat = "yyyy-MM-dd(EEEEE) a HH:mm" | ||
|
||
return formatter.string(from: Date(timeIntervalSince1970: dt)) | ||
} | ||
|
||
var weatherMain: String { weather.main } | ||
var description: String { weather.description } | ||
|
||
var temp: Double { main.temp } | ||
var tempMax: Double { main.tempMax } | ||
var tempMin: Double { main.tempMin } | ||
var feelsLike: Double { main.feelsLike } | ||
|
||
var pop: String { "\(main.pop * 100)%" } | ||
var humidity: String { "\(main.humidity)%" } | ||
|
||
var iconName: String { weather.icon } | ||
} | ||
|
||
// MARK: - MainClass | ||
struct MainInfo: Decodable { | ||
let temp: Double | ||
let feelsLike: Double | ||
let tempMin: Double | ||
let tempMax: Double | ||
let pressure: Double | ||
let seaLevel: Double | ||
let grndLevel: Double | ||
let humidity: Double | ||
let pop: Double | ||
} | ||
|
||
// MARK: - Weather | ||
struct Weather: Decodable { | ||
let id: Int | ||
let main: String | ||
let description: String | ||
let icon: String | ||
} | ||
|
||
// MARK: - City | ||
struct City: Decodable { | ||
let id: Int | ||
let name: String | ||
let coord: Coord | ||
let country: String | ||
let population, timezone: Int | ||
let sunrise, sunset: TimeInterval | ||
|
||
var sunriseString: String { | ||
let formatter: DateFormatter = DateFormatter() | ||
formatter.dateFormat = .none | ||
formatter.timeStyle = .short | ||
formatter.locale = .init(identifier: "ko_KR") | ||
return formatter.string(from: Date(timeIntervalSince1970: sunrise)) | ||
} | ||
|
||
var sunsetString: String { | ||
let formatter: DateFormatter = DateFormatter() | ||
formatter.dateFormat = .none | ||
formatter.timeStyle = .short | ||
formatter.locale = .init(identifier: "ko_KR") | ||
return formatter.string(from: Date(timeIntervalSince1970: sunset)) | ||
} | ||
} | ||
|
||
// MARK: - Coord | ||
struct Coord: Decodable { | ||
let lat: Double | ||
let lon: Double | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// | ||
// NetworkError.swift | ||
// WeatherForecast | ||
// | ||
// Created by MIN SEONG KIM on 2024/02/02. | ||
// | ||
|
||
import Foundation | ||
|
||
enum JsonError: Error { | ||
case emptyData // 데이터 미존재 | ||
case failDecode // 디코드 실패 | ||
} | ||
Comment on lines
+10
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CustomStringConvertible 프로토콜로 주석에 있는 설명을 description으로 옮겨보는건 어떨까요? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// | ||
// ImageServiceError.swift | ||
// WeatherForecast | ||
// | ||
// Created by MIN SEONG KIM on 2024/02/02. | ||
// | ||
|
||
import Foundation | ||
|
||
enum NetworkError: Error { | ||
case invalidUrl // url 에러 | ||
case networkFail // 응답상태코드 에러 | ||
case invalidData // 유효하지 않은 데이터 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// | ||
// NetworkService.swift | ||
// WeatherForecast | ||
// | ||
// Created by MIN SEONG KIM on 2024/02/02. | ||
// | ||
|
||
import Foundation | ||
|
||
protocol JsonService { | ||
func fetchWeather() async -> Result<WeatherJSON, JsonError> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// | ||
// ImageService.swift | ||
// WeatherForecast | ||
// | ||
// Created by MIN SEONG KIM on 2024/02/02. | ||
// | ||
|
||
import UIKit | ||
|
||
protocol NetworkService { | ||
func fetchImage(iconName: String, | ||
urlSession: URLSession) async throws -> UIImage | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// | ||
// TempUnitManagerService.swift | ||
// WeatherForecast | ||
// | ||
// Created by MIN SEONG KIM on 2024/02/06. | ||
// | ||
|
||
import Foundation | ||
|
||
protocol TempUnitManagerService { | ||
var tempUnit: TempUnit { get } | ||
|
||
func update() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// | ||
// TempUnitManager.swift | ||
// WeatherForecast | ||
// | ||
// Created by MIN SEONG KIM on 2024/02/06. | ||
// | ||
|
||
import Foundation | ||
|
||
final class TempUnitManager: TempUnitManagerService { | ||
var tempUnit: TempUnit = .metric | ||
|
||
func update() { | ||
switch tempUnit { | ||
case .metric: | ||
tempUnit = .imperial | ||
case .imperial: | ||
tempUnit = .metric | ||
} | ||
} | ||
|
||
|
||
} | ||
Comment on lines
+10
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 음.. 이 부분은 tempUnit을 바꾸기 위해 프로토콜과 class까지 생성되어야 하는 의문이 들어요!
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// | ||
// WeatherIconImageService.swift | ||
// WeatherForecast | ||
// | ||
// Created by MIN SEONG KIM on 2024/02/02. | ||
// | ||
|
||
import UIKit | ||
|
||
final class WeatherIconImageService: NetworkService { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 캐시를 관리하는 객체와 이미지를 다운받는 객체를 분리하는건 어떨까요? |
||
private let imageChache: NSCache<NSString, UIImage> = NSCache() | ||
|
||
func fetchImage(iconName: String, | ||
urlSession: URLSession) async throws -> UIImage { | ||
let urlString: String = "https://openweathermap.org/img/wn/\(iconName)@2x.png" | ||
|
||
guard let url: URL = URL(string: urlString) else { | ||
throw NetworkError.invalidUrl | ||
} | ||
|
||
let (data, response) = try await urlSession.data(from: url) | ||
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else { | ||
throw NetworkError.networkFail | ||
} | ||
|
||
guard let image: UIImage = UIImage(data: data) else { | ||
throw NetworkError.invalidData | ||
} | ||
|
||
imageChache.setObject(image, forKey: urlString as NSString) | ||
|
||
return image | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// | ||
// NetworkService.swift | ||
// WeatherForecast | ||
// | ||
// Created by MIN SEONG KIM on 2024/02/02. | ||
// | ||
|
||
import UIKit | ||
|
||
final class WeatherJsonService: JsonService { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
func fetchWeather() async -> Result<WeatherJSON, JsonError> { | ||
let jsonDecoder: JSONDecoder = .init() | ||
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase | ||
|
||
guard let data = NSDataAsset(name: "weather")?.data else { | ||
return .failure(.emptyData) | ||
} | ||
|
||
let info: WeatherJSON | ||
do { | ||
info = try jsonDecoder.decode(WeatherJSON.self, from: data) | ||
return .success(info) | ||
} catch { | ||
return .failure(.failDecode) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rawValue를 사용하는 곳은 없어보이는데, String으로 선언하신 이유�는 무엇일까요?