SpartaCamp CurrencyConverter – 트러블 슈팅

Ios_Roy·2025년 10월 2일
0

TIL

목록 보기
25/25
post-thumbnail

Level 8 (환율 변동 아이콘)과 Level 10 (앱 상태 저장·복원)을 동시에 구현하면서 고민했던 설계, 데이터 흐름, 메모리 전략을 정리했습니다.


목차

  1. 작업 개요
  2. Level 8 – 환율 트렌드 아이콘
  3. Level 10 – 앱 상태 저장 & 복원
  4. SwiftData 저장 전략
  5. 문서 & 프로세스 메모
  6. 향후 개선 아이디어
  7. 주요 코드 스냅샷
  8. 변경 이력
  9. 마무리

Level 8 – 환율 트렌드 아이콘

문제 정의

  • 환율은 소숫점 셋째 자리까지 변동이 잦아, 사용자 입장에서 “좋아진 건지 나빠진 건지”를 한눈에 파악하기 힘듭니다.
  • 단순 숫자 나열 대신, 이전 값 대비 변화 방향을 시각화해서 의미를 전달하고자 했습니다.

해결 전략

  1. 캐시된 환율 스냅샷을 우선 로드하고, 최신 데이터를 받은 뒤 차이를 계산합니다.
  2. 변동량이 ±0.01을 넘으면 상승(🔼)/하락(🔽) 아이콘을 보여 주고, 그렇지 않으면 .none으로 처리하여 UI 흔들림을 방지합니다.
  3. ProductCell에서 아이콘을 숫자 오른쪽으로 배치해 가독성을 높였습니다.

Level 10 – 앱 상태 저장 & 복원

목표

  • 앱이 재시작돼도 사용자가 보던 화면(리스트/계산기)으로 바로 복귀.
  • 계산기 화면이라면 “어떤 통화를 보고 있었는지”만 복원하고, 입력 숫자 등은 초기화.
  • 환율 데이터는 네트워크 지연을 고려해 캐시된 스냅샷으로 빠르게 표시.

고민 포인트

  • 무엇을 저장할 것인가? 화면 종류와 통화 정보만 저장, 입력 필드는 저장하지 않음.
  • 언제 저장·복원할 것인가? SceneDelegate에서 앱 배경/종료 시점에 저장, 런치 시점에 복원.
  • 데이터 정합성: 오래된 스냅샷이 남지 않도록 단일 엔티티 정책을 유지.

SwiftData 저장 전략

모델링

  • 엔티티를 최소화하여 메모리와 디스크 사용량을 예측 가능하게 유지합니다.
@Model
final class LastViewedScreenEntity {
  @Attribute(.unique) var id: String = "singleton"
  var type: ScreenType
  var currencyCode: String?
  var exchangeRate: Double?

  init(type: ScreenType, currencyCode: String?, exchangeRate: Double?) {
    self.type = type
    self.currencyCode = currencyCode
    self.exchangeRate = exchangeRate
  }
}

저장 시나리오

  • 지연 로드: 앱 기동 시 마지막 화면만 불러오고, 환율 스냅샷은 실제 필요 시점까지 미룸.
  • 배경 저장: UI 스레드를 막지 않도록 Task.detached에서 저장/삭제를 실행.
  • 단일 스냅샷 정책: 새 데이터를 저장하기 전에 기존 엔티티를 삭제해 데이터가 누적되지 않도록 함.
func persistLastScreen(_ screen: LastViewedScreen, context: ModelContext) async {
  await context.perform {
    try? context.delete(model: LastViewedScreenEntity.self)
    let entity = LastViewedScreenEntity(
      type: screen.type,
      currencyCode: screen.currencyCode,
      exchangeRate: screen.exchangeRate
    )
    context.insert(entity)
    try? context.save()
  }
}

환율 스냅샷 관리

  • ExchangeRateSnapshotEntity를 하나만 유지해 디스크 사용량과 fetch 비용을 일정하게 유지.
  • 캐시가 낡았을 때만 네트워크를 호출해 불필요한 API 사용을 줄였습니다.

주요 코드 스냅샷

환율 트렌드 계산

private func computeTrends(
  newRates: [String: Double],
  previousRates: [String: Double]
) -> [String: RateTrend] {
  newRates.reduce(into: [String: RateTrend]()) { result, entry in
    let previous = previousRates[entry.key] ?? entry.value
    let diff = entry.value - previous
    result[entry.key] = RateTrend(difference: diff)
  }
}

환율 샘플 생성 유틸리티

let snapshot = ExchangeRates.sample(
  base: "USD",
  rates: ["KRW": 1350.25, "JPY": 148.56],
  timestamp: Date(timeIntervalSince1970: 1_704_000_000)
)

let updated = snapshot.updating(
  base: "EUR",
  rates: ["USD": 1.08, "KRW": 1430.5]
)

마무리

작업은 단순히 기능을 추가하는 수준이 아니라, 데이터를 어떻게 해석하게 만들고, 사용자의 맥락을 어떻게 지켜 줄 것인가에 대한 고민이었습니다.

  • 트렌드 아이콘으로 숫자에 스토리를 부여했고,
  • 상태 복원과 SwiftData 전략으로 앱이 “사용자를 기억”하도록 만들었습니다.

앞으로도 기능 구현을 넘어서, 데이터와 UX 사이의 연결 고리를 고민하는 개발자로 성장하고 싶습니다.

profile
iOS 개발자 공부하는 Roy

0개의 댓글