ios 36일차

bin·2026년 2월 20일

오늘은 다룰 내용이 많다보니 바로 과제 진행을 통해 겪은 문제점과 구상안들에 대해 정리할 것이다.
어제 저녁과 오늘 진행한 과제는 레벨8, 레벨9, 레벨 10이다. 레벨9는 어제 저녁에 금방 완성하기도 했고, 다크모드를 위한 색상 지정에 관련된 간단한 내용이기에 스킵하고 레벨8, 레벨10에 대해 정리해보자.

과제에 관한 기록

LV. 8 상승 🔼 하락 🔽 여부 표시

요구사항은 간단하다. 앱에 접근했을 때, 이전 날에 받아온 데이터와 비교하여 현재의 가격이 조건보다 크면 상승 🔼, 조건보다 작으면 하락 🔽을 표시해야하며, 둘 다 해당되지 않을 시 공란으로 처리한다.(비어있게 처리하더라도 공간을 유지해서 글자의 위치가 일정하게 유지되도록 구현해야 함.)
일단, 데이터를 비교하기 위해 정보를 어떻게 저장할 것인가에 대해 생각해보았다. 당일에 여러번 접속을 할 수 있는데, 그럴 경우 매번 데이터를 DB에 저장하는 것은 낭비일 것이다. 그렇다면 어제와 오늘의 구분은 어떻게 진행해야 할까?

  • API를 호출하는 경로에 접근하여 받아오는 실제 데이터를 확인해보니 아래와 같이 날짜를 구분하기 위해 사용할 만한 데이터를 찾았다.
"time_last_update_unix": 1771545751,
"time_last_update_utc": "Fri, 20 Feb 2026 00:02:31 +0000",
"time_next_update_unix": 1771632781,
"time_next_update_utc": "Sat, 21 Feb 2026 00:13:01 +0000",
  • 본인은 이 데이터들 중 time_last_update_unix를 이용해서 조건문을 통해 데이터를 저장, 삭제하는 로직을 구현하고자 했다. 4개의 데이터들 중 이를 선택한 이유는 별도의 처리 없이 Int형으로 저장하여 값을 확인하기 편할 것이며 현재 업데이트된 상태를 나타내기 때문이다.

1. CurrencyResponse.swift

언제 업데이트가 되었는가를 나타내는 데이터를 받기 위해 unix를 추가한다. unix는 실제 API 호출 시 time_last_update_unix로 응답하기에 Codingkeys를 사용하여 unix에 넣어준다.(Int가 아니라 Int64로 사용해야 함: CoreData의 Attributes Type 지정에는 Int16, 32, 64가 존재하기 때문)

2. HistoryManager.swift

받아온 데이터를 저장하는 메서드를 생성해야 한다.
데이터는 어제와 오늘 정확하게 두개만 존재하면 된다. 앱을 처음 사용할 경우 오늘의 데이터 하나만 존재할 수는 있어도, 두 개 이상은 존재할 필요가 없으며 본인의 조건에 따라서는 존재하면 안된다. 그러므로 데이터의 개수가 2개 이상일 경우 data 배열의 첫번째 데이터를 delete하고 다시 새로운 데이터를 넣는 방식으로 구현했다. 위와 같은 구현을 위해서는 받아온 데이터를 unix를 기준으로 오름차순으로 정렬하는 로직이 필요하다. 넘겨받는 데이터는 Set과 같이 순서가 보장되지 않으므로 증가하는 unix 값의 특징을 이용하여 배열을 unix 기준 오름차순으로 정렬하여 가장 오래된 값을 제거한다.

// 같은 날에 받아온 데이터라면 저장하지 않음.
if data.contains(where: { $0.unix == unix }) { return }
// 데이터의 개수가 2개 이상일 경우(2일 이상 경과)
if data.count >= 2 { self.container.viewContext.delete(data.first!) }

2.1 saveData()

ViewModel에서 사용하게되는 CoreData에서 데이터를 불러오는 메서드이다. 해당 메서드에서는 가장 최신의 데이터 하나만을 필요로하기 때문에 unix를 기준으로 내림차순으로 정렬한다. 또한 fetchLimit을 1로 제한하여 데이터를 하나 찾게되면 바로 fetch를 멈추도록 구현했다.

func fetchData(code: String) -> History? {
	let fetchRequest = History.fetchRequest()
	fetchRequest.predicate = NSPredicate(format: "code == %@", code)
		fetchRequest.sortDescriptors = [NSSortDescriptor(key: "unix", ascending: false)] // unix를 기준으로 내림차순 정렬(큰 값이 가장 최신)
        fetchRequest.fetchLimit = 1 // 데이터 하나를 찾으면 바로 멈춤
        do {
            let data = try self.container.viewContext.fetch(fetchRequest)
            return data.first
        } catch {
            print("데이터 조회 실패")
            return nil
        }
    }

3. ExchangeRate.swift

셀에 데이터를 표시하기 위해 사용하는 ExchangeRate에 상승과 하락의 상태를 표시할 이미지를 지정할 enum을 생성한다.

enum RateStatus {
    case up, down, stay
}

이를 ExchangeRate에 status를 선언하여 사용한다. 기본값은 .stay로 지정한다.

4. TableViewCell.swift

기본적인 UIImageView생성은 기록하지 않는다. 변경 사항으로는 config 메서드의 매개변수를 code, rate를 사용하지 않고 item(ExchangeRate)으로 넘겨받으며 item.status에 따라 case를 구분하여 이미지를 지정한다.

switch item.status {
	case .up:
		statusImage.image = UIImage(systemName: "arrowtriangle.up.square.fill")
	case .down:
		statusImage.image = UIImage(systemName: "arrowtriangle.down.square.fill")
	case .stay:
		statusImage.image = nil
}

5. ExchangeRateViewModel.swift

지금까지 생성한 historyManager를 사용하기 위해 선언한다.

var historyManager: HistoryManager?

이제 API를 호출하는 로직에 정보를 처리해야 한다. (API 호출 시 받아온 데이터를 DB에 저장된 과거 환율과 비교한 후 viewData에 저장한다. 이후 DB에 다시 저장한다.)
데이터를 받아오는 로직이 성공했을 경우, 받아온 result 값을 통해 rates와 unix에 접근할 수 있다. sortedRates를 map을 통해 작업을 진행한 후 allData에 담아둔다. 가장 최근 데이터를 받아와서 lastData에 담아둔 후 lastData의 unix 값과 currentUnix값을 비교하여 조건문을 처리한다.

case .success(let result):
	let sortedRates = result.rates.sorted{ $0.key < $1.key }
	let currentUnix = result.unix
	allData = sortedRates.map { code, rate in
		let lastData = self.historyManager?.fetchData(code: code)
		var exchangeRate = ExchangeRate(code: code, rate: rate)
		if let last = lastData, last.unix != Int64(currentUnix) {
			let diff = rate - last.rate
			if diff > 0.01 {
				exchangeRate.status = .up
				} else if diff < -0.01 {
					exchangeRate.status = .down
				}
			}
			return exchangeRate
		}
		viewData = allData
		sortByFavorite()
		self.historyManager?.saveData(rates: result.rates, unix: Int64(currentUnix))

6. ExchangeRateViewController.swift

historyManager를 생성하고 이를 viewModel에 연결해준다.
DataSource의 cellForRowAt 내부의 cell.config를 수정한다.

7. 구상안 및 트러블

"큰 문제를 겪었다"라기보다는 문제에 어떻게 접근을 하는게 보다 효율적으로 DB를 사용할 수 있을까?에 초점을 두었다. 물론 현재 작성한 코드가 100% 효율적이며 완성도가 높다고 할 수 없지만, 시간을 두고 천천히 생각해보며 완성해낸 결과물이라고 생각한다. LV.8을 진행하며 구상했던 생각과 겪은 문제를 적어둔 내용을 저장하고 기록하고자 한다.

트러블 발생
데이터 저장을 어떻게 할 것인가?
구상 {
비교를 위해 데이터는 총 2개만 있으면 된다.
비교는 어떤 것을 기준으로 할 것인가?
-> 받아오는 데이터의 unix값을 기준으로 하자 ( 날짜가 변경되어 데이터가 달라지면 time_last_update_unix가 달라진다. 이 값을 받아서 coreData에 저장하자 )
fetchRequest, predicate를 사용하여 code에 대해 조건을 걸고 데이터를 받아온다.
받아온 배열은 순서를 보장하지 않기에 sortDescriptors를 사용하여 updateUnix를 기준으로 오름차순으로 정렬한다. (unix값은 계속 커짐)
history에 받아온 데이터 배열을 담아두고,
만약 history에 현재 unix가 존재한다면, 같은 날 api를 호출한 것이기에 return (pass)
만약 history의 크기가 2보다 크거나 같으면 history배열의 첫번째 요소를 제거한다.(가장 오래된 데이터)
이후 데이터 저장
}

LV. 10 앱 상태 저장 및 복원

시간이 부족한 관계로 다음에 정리하도록 하겠다.

0개의 댓글