Skip to content

Commit

Permalink
Merge branch 'feedback' of https://github.com/Jae-eun/team-c2 into fe…
Browse files Browse the repository at this point in the history
…edback
  • Loading branch information
Jae-eun committed Feb 11, 2019
2 parents b3581dc + 428e013 commit 7bf6d0c
Show file tree
Hide file tree
Showing 37 changed files with 488 additions and 256 deletions.
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ excluded:
- FineDust/Supporting Files/AppDelegate.swift
- FineDust/Supporting Files/GeoConverter.swift
- Pods/
- FineDustTests/

line_length:
warning: 99
Expand Down
42 changes: 30 additions & 12 deletions FineDust.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions FineDust/Common/Typealias.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ import Foundation

/// 시간대별 흡입량 타입 별칭.
typealias HourIntakePair = [Hour: Int]

typealias DateHourIntakePair = [Date: HourIntakePair]
4 changes: 2 additions & 2 deletions FineDust/Core Data/CoreData+User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import Foundation
/// `User` Entity Attribute 상수 정리
extension User {

/// 설치 날짜 Attribute
static let installedDate = "installedDate"
/// 최근 접속 날짜 Attribute
static let lastAccessedDate = "lastAccessedDate"
}

// MARK: - CoreDataUserManagerType 준수
Expand Down
6 changes: 3 additions & 3 deletions FineDust/Core Data/CoreDataIntakeManagerType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ import Foundation
protocol CoreDataIntakeManagerType: CoreDataManagerType {

/// READ
func fetch(completion: (Intake?, Error?) -> Void)
func request(completion: ([Intake]?, Error?) -> Void)
}

// MARK: - CoreDataIntakeManagerType 프로토콜 초기 구현

extension CoreDataIntakeManagerType {

func fetch(completion: (Intake?, Error?) -> Void) {
func request(completion: ([Intake]?, Error?) -> Void) {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: Intake.classNameToString)
do {
let results = try context.fetch(request) as? [Intake]
completion(results?.first, nil)
completion(results, nil)
} catch {
completion(nil, error)
}
Expand Down
59 changes: 53 additions & 6 deletions FineDust/Core Data/CoreDataService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,71 @@ import Foundation

/// 코어데이터 서비스 클래스.
final class CoreDataService: CoreDataServiceType {

/// CoreDataService 싱글톤 객체.
static let shared = CoreDataService()

/// `User` Entity가 들어올, CoreDataManagerType을 준수하는 프로퍼티.
let user: CoreDataManagerType
let user: CoreDataUserManagerType

/// `Intake` Entity가 들어올, CoreDataManagerType을 준수하는 프로퍼티.
let intake: CoreDataManagerType
let intake: CoreDataIntakeManagerType

private init() {
let context = CoreDataManager.shared.context
user = User(context: context)
intake = Intake(context: context)
}

func fetchIntakesInWeek(since date: Date, completion: @escaping ([Int]?, Error?) -> Void) {
let array = [1, 2, 3, 4, 5, 6, 7]
completion(array, nil)
func saveLastAccessedDate(completion: @escaping (Error?) -> Void) {
user.save([User.lastAccessedDate: Date()], completion: completion)
}

func requestLastAccessedDate(completion: @escaping (Date?, Error?) -> Void) {
user.request { user, error in
// 최신 접속 날짜가 코어데이터에 저장되어 있으면 그 값을 내려줌
// 그렇지 않으면 최신 접속 날짜를 갱신한 후 그 값을 내려줌
if let lastAccessedDate = user?.lastAccessedDate {
completion(lastAccessedDate, error)
} else {
saveLastAccessedDate { error in
completion(Date.start(), error)
}
}
}
}

func requestIntakes(from startDate: Date,
to endDate: Date,
completion: @escaping ([Date: Int?]?, Error?) -> Void) {
intake.request { intakes, error in
if let error = error {
completion(nil, error)
return
}
guard let intakes = intakes else { return }
var result: [Date: Int?] = [:]
let startDate = startDate.start
let endDate = endDate.end
let intakesInDates = intakes.filter { (startDate...endDate).contains($0.date ?? Date()) }
var currentDate = startDate
// 인자에 들어온 날짜를 순회하면서
// 코어데이터에 해당 날짜에 대한 정보가 저장되어 있으면 그 정보를 내려주고
// 그렇지 않으면 nil을 내려주어 해당 부분은 통신으로 처리하게 함
while currentDate <= endDate.end {
let intakeInCurrentDate = intakesInDates.filter { $0.date?.start == currentDate }.first
if let currentIntake = intakeInCurrentDate {
result[currentDate] = Int(currentIntake.value)
} else {
result[currentDate] = nil
}
currentDate = currentDate.after(days: 1).start
}
completion(result, nil)
}
}

func saveIntake(_ value: Int, at date: Date, completion: @escaping (Error?) -> Void) {
intake.save([Intake.date: Int16(value)], completion: completion)
}
}
13 changes: 12 additions & 1 deletion FineDust/Core Data/CoreDataServiceType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ import Foundation
/// Core Data Service 프로토콜.
protocol CoreDataServiceType: class {

/// 최근 접속 날짜 저장.
func saveLastAccessedDate(completion: @escaping (Error?) -> Void)

/// 최근 접속 날짜 가져오기.
func requestLastAccessedDate(completion: @escaping (Date?, Error?) -> Void)

/// 일주일 미세먼지 흡입량 가져오기.
func fetchIntakesInWeek(since date: Date, completion: @escaping ([Int]?, Error?) -> Void)
func requestIntakes(from startDate: Date,
to endDate: Date,
completion: @escaping ([Date: Int?]?, Error?) -> Void)

/// 특정 날짜에 대한 미세먼지 흡입량 저장.
func saveIntake(_ value: Int, at date: Date, completion: @escaping (Error?) -> Void)
}
4 changes: 2 additions & 2 deletions FineDust/Core Data/CoreDataUserManagerType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import Foundation
protocol CoreDataUserManagerType: CoreDataManagerType {

/// READ
func fetch(completion: (User?, Error?) -> Void)
func request(completion: (User?, Error?) -> Void)
}

// MARK: - CoreDataUserManagerType 프로토콜 초기 구현

extension CoreDataUserManagerType {

func fetch(completion: (User?, Error?) -> Void) {
func request(completion: (User?, Error?) -> Void) {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: User.classNameToString)
do {
let results = try context.fetch(request) as? [User]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
<relationship name="intake" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="intake" inverseEntity="User" syncable="YES"/>
</entity>
<entity name="User" representedClassName="User" syncable="YES" codeGenerationType="class">
<attribute name="installedDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="lastAccessedDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<relationship name="intake" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Intake" inverseName="intake" inverseEntity="Intake" syncable="YES"/>
</entity>
<elements>
<element name="User" positionX="-238.1171875" positionY="-28.01171875" width="128" height="73"/>
<element name="Intake" positionX="-18" positionY="27" width="128" height="88"/>
<element name="User" positionX="-238.1171875" positionY="-28.01171875" width="128" height="75"/>
</elements>
</model>
16 changes: 8 additions & 8 deletions FineDust/Dust/DustInfoManagerType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ import Foundation

/// 미세먼지 정보 관련 Dust Manager 프로토콜.
protocol DustInfoManagerType: DustManagerType {
func fetchDustInfo(term dataTerm: DataTerm,
numberOfRows numOfRows: Int,
pageNumber pageNo: Int,
completion: @escaping (DustResponse?, Error?) -> Void)
func request(dataTerm: DataTerm,
numberOfRows numOfRows: Int,
pageNumber pageNo: Int,
completion: @escaping (DustResponse?, Error?) -> Void)
}

// MARK: - DustInfoManagerType 프로토콜 초기 구현

extension DustInfoManagerType {
func fetchDustInfo(term dataTerm: DataTerm,
numberOfRows numOfRows: Int,
pageNumber pageNo: Int,
completion: @escaping (DustResponse?, Error?) -> Void) {
func request(dataTerm: DataTerm,
numberOfRows numOfRows: Int,
pageNumber pageNo: Int,
completion: @escaping (DustResponse?, Error?) -> Void) {
let observatory = SharedInfo.shared.observatory.percentEncoded
let urlString = baseURL
.appending("/ArpltnInforInqireSvc/getMsrstnAcctoRltmMesureDnsty")
Expand Down
115 changes: 87 additions & 28 deletions FineDust/Dust/DustInfoService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,7 @@ import Foundation
final class DustInfoService: DustInfoServiceType {

// MARK: Property

/// `yyyy-MM-dd HH:mm` 형식으로 포매팅하는 데이트 포매터.
private lazy var fullDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm"
return formatter
}()

/// `HH` 형식으로 포매팅하는 데이트 포매터.
private lazy var monthDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "HH"
return formatter
}()


/// 미세먼지 매니저 프로토콜을 준수하는 프로퍼티.
let dustInfoManager: DustInfoManagerType

Expand All @@ -36,10 +22,10 @@ final class DustInfoService: DustInfoServiceType {
self.dustInfoManager = dustManager
}

func fetchRecentTimeInfo(_ completion: @escaping (RecentDustInfo?, Error?) -> Void) {
func requestRecentTimeInfo(_ completion: @escaping (RecentDustInfo?, Error?) -> Void) {
dustInfoManager
.fetchDustInfo(
term: .daily,
.request(
dataTerm: .daily,
numberOfRows: 1,
pageNumber: 1) { response, error in
if let error = error {
Expand All @@ -51,41 +37,47 @@ final class DustInfoService: DustInfoServiceType {
fineDustValue: recentResponse.fineDustValue,
ultrafineDustValue: recentResponse.ultrafineDustValue,
fineDustGrade: DustGrade(rawValue: recentResponse.fineDustGrade) ?? .default,
ultrafineDustGrade: DustGrade(rawValue: recentResponse.ultrafineDustGrade) ?? .default,
updatingTime: self.fullDateFormatter.date(from: recentResponse.dataTime) ?? Date()
ultrafineDustGrade: DustGrade(
rawValue: recentResponse.ultrafineDustGrade) ?? .default,
updatingTime: DateFormatter.dateAndTimeForDust
.date(from: recentResponse.dataTime) ?? Date()
)
completion(dustInfo, nil)
}
}
}

func fetchTodayInfo(_ completion: @escaping (HourIntakePair?, HourIntakePair?, Error?) -> Void) {
func requestDayInfo(_ completion: @escaping (HourIntakePair?, HourIntakePair?, Error?) -> Void) {
dustInfoManager
.fetchDustInfo(
term: .daily,
.request(
dataTerm: .month,
numberOfRows: 24,
pageNumber: 1) { response, error in
var fineDust: [Hour: Int] = [:]
var ultrafineDust: [Hour: Int] = [:]
var fineDust: HourIntakePair = [:]
var ultrafineDust: HourIntakePair = [:]
if let error = error {
completion(nil, nil, error)
return
}
guard let items = response?.items else { return }
for item in items {
let hour: Hour
if let dataTimeToDate = self.fullDateFormatter.date(from: item.dataTime) {
let hourToString = self.monthDateFormatter.string(from: dataTimeToDate)
if let dataTimeToDate = DateFormatter.dateAndTimeForDust.date(from: item.dataTime) {
let hourToString = DateFormatter.hour.string(from: dataTimeToDate)
let hourToInt = Int(hourToString) ?? 0
hour = Hour(rawValue: hourToInt) ?? .default
} else {
// 24:00 이라서 데이트 파싱이 안되고 nil이 나오는 경우
// 이 메소드에서는 그냥 0시인 것처럼 처리하면 됨
hour = Hour(rawValue: 0) ?? .default
}
fineDust[hour] = item.fineDustValue
ultrafineDust[hour] = item.ultrafineDustValue
// 0시에 대한 처리가 끝난 경우 반복문 탈출
if hour == .zero { break }
}
Hour.allCases.forEach { hour in
// 딕셔너리의 길이를 맞추기 위한 코드
Hour.allCases.filter { $0 == .default }.forEach { hour in
if fineDust[hour] == nil {
fineDust[hour] = 0
}
Expand All @@ -96,4 +88,71 @@ final class DustInfoService: DustInfoServiceType {
completion(fineDust, ultrafineDust, nil)
}
}

func requestDayInfo(
from startDate: Date,
to endDate: Date,
completion: @escaping (DateHourIntakePair?, DateHourIntakePair?, Error?) -> Void
) {
// 시작 날짜와 끝 날짜의 간격 구하기
let calendar = Calendar.current
let daysBetweenDates
= calendar.dateComponents([.day], from: startDate.start, to: endDate.start).day ?? 0
dustInfoManager
.request(
dataTerm: .month,
numberOfRows: (daysBetweenDates + 2) * 24,
pageNumber: 1) { response, error in
var fineDustPerDate: DateHourIntakePair = [:]
var ultrafineDustPerDate: DateHourIntakePair = [:]
if let error = error {
completion(nil, nil, error)
return
}
guard let items = response?.items else { return }
for item in items {
let hour: Hour
// 현재 요소의 `dataTime`의 0시 날짜
let currentStartDate: Date
if let dataTimeToDate = DateFormatter.dateAndTimeForDust.date(from: item.dataTime) {
// 24:00 형식이 아니어서 데이트 파싱이 잘 되는 경우
// 하던 대로 한다
//
// dataTime에서 시간을 추출하고, 그것으로 `Hour` 열거형 인스턴스를 생성한다
// 최종 결과 딕셔너리의 키값으로 쓰일 `Date`는 dataTime의 시작 날짜로 한다
let hourToString = DateFormatter.hour.string(from: dataTimeToDate)
let hourToInt = Int(hourToString) ?? 0
hour = Hour(rawValue: hourToInt) ?? .default
currentStartDate = dataTimeToDate.start
} else {
// 24:00 이라서 데이트 파싱이 안되고 nil이 나오는 경우
// 다음 날짜의 0시로 바꿔준다
//
// 예를 들어 2019-01-01 24:00을 2019-01-02 00:00으로 바꿔주는 작업을 하는 것이다
// 이를 위해 dataTime을 공백을 기준으로 잘라 yyyy-MM-dd 형식만을 취하고
// 이 형식의 다음 날짜의 시작 날짜를 구하여 최종 결과 딕셔너리의 키값으로 활용한다
// 이 경우 어차피 0시가 될 것이므로 `Hour` 열거형의 인스턴스는 원시값 0으로 생성한다
let halfDataTime = item.dataTime.components(separatedBy: " ").first ?? ""
let halfDataTimeToDate = DateFormatter.dateForDust.date(from: halfDataTime)
let nextHalfDataTime = halfDataTimeToDate?.after(days: 1)
let nextMidnight = nextHalfDataTime?.start ?? Date()
hour = Hour(rawValue: 0) ?? .default
currentStartDate = nextMidnight
}
// 인자로 들어온 Date의 구간 내에 포함되어 있지 않으면 필요 없으므로 continue
if !(startDate.start...endDate.start).contains(currentStartDate) { continue }
// 시작 날짜의 전날 시작 날짜와 현재 요소의 시작 날짜가 같으면 필요한 처리를 다 한 것이므로 break
if startDate.before(days: 1).start == currentStartDate { break }
if fineDustPerDate[currentStartDate] == nil {
fineDustPerDate[currentStartDate] = [:]
}
fineDustPerDate[currentStartDate]?[hour] = item.fineDustValue
if ultrafineDustPerDate[currentStartDate] == nil {
ultrafineDustPerDate[currentStartDate] = [:]
}
ultrafineDustPerDate[currentStartDate]?[hour] = item.ultrafineDustValue
}
completion(fineDustPerDate, ultrafineDustPerDate, nil)
}
}
}
9 changes: 7 additions & 2 deletions FineDust/Dust/DustInfoServiceType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ import Foundation
protocol DustInfoServiceType: class {

/// 최근 시간의 미세먼지 관련 정보 fetch.
func fetchRecentTimeInfo(_ completion: @escaping (RecentDustInfo?, Error?) -> Void)
func requestRecentTimeInfo(_ completion: @escaping (RecentDustInfo?, Error?) -> Void)

/// 하루의 미세먼지 관련 정보를 fetch하고 시간대별 미세먼지 값과 초미세먼지 값을 산출.
func fetchTodayInfo(_ completion: @escaping (HourIntakePair?, HourIntakePair?, Error?) -> Void)
func requestDayInfo(_ completion: @escaping (HourIntakePair?, HourIntakePair?, Error?) -> Void)

/// 특정 날짜부터 특정 날짜까지의 미세먼지 관련 정보를 fetch하고 시간대별 미세먼지 값과 초미세먼지 값을 산출.
func requestDayInfo(from startDate: Date,
to endDate: Date,
completion: @escaping (DateHourIntakePair?, DateHourIntakePair?, Error?) -> Void)
}
Loading

0 comments on commit 7bf6d0c

Please sign in to comment.