나는 Swift, SQLite, 그리고 Claude Code를 사용해 43일 만에 오프라인 우선 iOS 앱을 만들었습니다.

발행: (2026년 4월 3일 PM 02:10 GMT+9)
12 분 소요
원문: Dev.to

It looks like only the source line was provided. Could you please share the article text you’d like translated into Korean? Once I have the content, I’ll keep the source link unchanged and translate the rest while preserving the original formatting.

개요

제 아내는 영국의 플리마켓과 차고 매장에서 중고 물품을 구입해 eBay와 Vinted에 재판매합니다. 그녀는 모든 것을 Google Sheets에 기록했지만, 물건을 산 가격을 계속 잊어버렸습니다. 누군가 제안을 보냈을 때, 그것이 좋은 거래인지 전혀 알 수 없었습니다.

그래서 저는 43 일 전에 그녀를 위해 iOS 앱을 만들었습니다. 현재 앱 스토어에 올라가 있으며 완전히 무료이고, 실제로 몇 명이 사용하고 있습니다. 아래는 제가 앱을 구축한 과정과 그 과정에서 내린 기술적 결정들을 단계별로 설명합니다.

앱이 하는 일

  1. Capture – 시장에서 사진을 찍고 구매 가격을 입력합니다.

  2. Track – 앱은 아이템의 전체 수명 주기를 추적합니다:

    purchased → listed on platforms → sold

  3. Calculate – 모든 비용(입장료, 운송비, 포장비) 후에 이익이 계산됩니다.

  4. Multi‑currency – 자동 환율 업데이트와 함께 네 가지 통화를 지원합니다.

  5. Offline‑first – 완전히 오프라인으로 작동하며, 네트워크는 선택적 동기화에만 필요합니다.

Tech stack

  • Swift + SwiftUI – iOS 17+
  • SQLite via GRDB – WAL 모드, 외래키, 복합 인덱스를 사용하는 로컬 스토리지
  • Google Drive API – 선택적 사진 동기화 + CSV 내보내기 (오직 drive.file 범위만 사용)
  • BGTaskScheduler – 백그라운드 사진 업로드
  • CoreLocation – 근처 마켓 자동 제안
  • No backend – 모든 것이 디바이스에서 실행되며, 서버 비용이 전혀 없음

아키텍처

SwiftUI Views

ViewModels (ObservableObject)

Services (GoogleAuth, Export, Location, Notification)

DatabaseStore (Swift Actor – all CRUD operations)

SQLite (GRDB, WAL mode) + Local Images

핵심 결정은 앱을 오프라인‑우선으로 만드는 것이었습니다. 카부트 판매는 종종 신호가 없는 야외에서 이루어지기 때문에 핵심 기능이 네트워크 연결에 의존할 수 없습니다. Google Drive 동기화는 순전히 선택 사항이며, Google 계정을 연결하지 않아도 앱을 영원히 사용할 수 있습니다.

Day 1–7: JSON 파일과 후회

가장 간단한 저장 방식으로 시작했습니다 – 앱의 Documents 디렉터리에 JSON 파일을 두는 방식(엔터티 유형당 하나의 파일: items, markets, sellers, expenses). 전체 파일을 메모리로 읽어들인 뒤 수정하고 다시 기록했습니다.

  • 약 20개의 아이템에서는 잘 동작합니다.
  • 약 200개의 아이템이 되면 매우 느려집니다.
  • 효율적인 쿼리가 없습니다(예: “날짜 순으로 정렬된 판매되지 않은 모든 아이템을 페이지별로 보여줘”).

Day 18: SQLite 마이그레이션

저는 모든 것을 GRDB(Swift SQLite 래퍼)로 마이그레이션했습니다. 이는 프로젝트에서 가장 중요한 하루치 변경이었습니다.

GRDB가 제공하는 것

  • 테이블에 매핑되는 타입이 지정된 레코드 구조체
  • 마이그레이션 시스템(버전 관리 스키마 변경)
  • 동시 읽기를 위한 WAL 모드
  • 빠른 쿼리를 위한 적절한 인덱스

현재까지의 마이그레이션 버전

// v1: Initial schema — all tables, indexes, foreign keys
// v2: Composite indexes for paginated list queries
// v3: Crockford Base32 SKU regeneration
// v5: Market "askForEntryFee" boolean column

페이지네이션된 쿼리는 “모두 메모리로 로드하고 필터링”에서 인덱스된 컬럼에 LIMIT/OFFSET을 적용한 실제 SQL로 바뀌어 – 밤과 낮의 성능 차이를 보였습니다.

오프라인 우선, 선택적 Google 동기화

동기화 모델은 의도적으로 단순합니다:

  1. 항목은 기본적으로 local 상태입니다.

  2. Google Drive 계정이 연결된 경우:

    • 사진은 SyncQueueService를 통해 업로드 대기열에 들어갑니다.
    • BGTaskScheduler는 앱이 일시 중지된 상태에서도 대기열을 처리합니다.
    • CSV 내보내기는 5분 타이머로 실행되며 MAX(updatedAt)와 마지막 동기화 타임스탬프를 비교해 데이터가 변경된 경우에만 업로드합니다.
    • CSV는 업로드 전에 gzip으로 압축됩니다.
    • Google이 CSV를 자동으로 Google Sheet로 변환합니다 (Sheets API가 필요 없습니다).

drive.file 범위만 사용하면 앱이 자체적으로 만든 파일에만 접근하도록 제한되어 프라이버시가 향상되고 OAuth가 간소화됩니다.

다중 통화와 환율

아내는 영국 시장에서 GBP로 구매하고 때때로 eBay에서 EUR 또는 USD로 판매합니다. 앱은 구매/판매 통화와 관계없이 사용자의 기본 통화로 이익을 표시해야 합니다.

  • 일일 환율은 무료 API에서 가져와 SQLite에 365 일 동안 캐시합니다.

  • 지원되는 변환 경로:

    1. 직접 환율 – 예: GBP → USD
    2. 역환율 – 예: USD → GBP (1 / rate)
    3. 공통 기준 통화를 통한 교차 환율 – 예: EUR → CAD via USD
  • 며칠 동안 오프라인 상태가 되면 앱은 캐시된 환율을 사용하고, 환율이 오래되었을 수 있다는 경고를 표시합니다. 사용자는 연결이 복구되면 수동으로 새로 고침할 수 있습니다.

SKU 생성

각 항목은 FH-20D6G0NR와 같은 고유 SKU를 받습니다.

  • 인코딩: Crockford Base32 (대문자만 사용, I, L, O, U와 같은 혼동되는 문자 제외).
  • 생성: 시간 기반 요소 + 원자 카운터가 고유성을 보장합니다.

사용자는 SKU를 eBay/Vinted 목록에 복사하여 나중에 판매를 원본 기록과 매칭할 수 있습니다.

Claude Code로 빌드하기

I used Claude Code throughout the entire build. It was essential for delivering the product in 43 days as a solo developer. The AI handled a lot of boilerplate:

  • GRDB 레코드 타입
  • SwiftUI 폼 레이아웃
  • CSV 생성
  • Google OAuth 흐름

빛을 발한 부분

  • JSON에서 SQLite로 마이그레이션 – 기존 모델과 목표 스키마를 설명했으며, Claude가 마이그레이션 코드, 새로운 레코드 타입, 그리고 업데이트된 쿼리를 생성했습니다.

부족했던 부분

  • 설계 결정(오프라인 vs. 동기화, 통화 변환 엣지 케이스, 기능 우선순위 지정)은 여전히 인간의 판단이 필요했습니다.

Figma 재디자인 (Day 23)

저는 디자이너는 아니지만, 앱이 개발자가 만든 것처럼 보였습니다. 먼저 Figma에서 proper 디자인 시스템을 만들었습니다:

  • 폰트: Roboto Flex (가변 굵기)

  • 디자인 토큰:

    • 골드 #F9DDA5
    • 배지 #BBCBF7
    • 카드 배경 #F8FAFB
    • 액센트 #334B90
  • 뉴모픽 그림자: 두 가지 스타일 – 큰 그림자 (반경 30)와 작은 그림자 (반경 15)

  • 라이트 모드 전용: 모든 곳에 명시적인 색상 사용, 시스템 적응 팔레트 없음

토큰을 정의한 뒤, 모든 화면을 Figma와 정확히 일치하도록 다시 만들었습니다. 1 px → 1 pt 매핑 덕분에 구현이 놀라울 정도로 간단했습니다.

내가 다음에 할 일

(미래 개선을 위해 섹션을 의도적으로 비워두었습니다.)

Source:

Lessons Learned

시작부터 SQLite 사용
JSON‑to‑SQLite 마이그레이션은 가치가 있었지만, 결국 버릴 코드를 만드는 데 시간을 낭비했습니다. 앱에 레코드가 50개 이상 들어갈 예정이라면 처음부터 데이터베이스를 사용하세요.

검색 기능을 더 일찍 추가
사용자들이 바로 요구했습니다. 모든 탭에서 제목이나 SKU로 아이템을 찾을 수 있게 하는 것이 나중에 보니 당연했습니다.

Google 연동을 과도하게 고민하지 말 것
동기화를 완벽하게 만들려다 너무 오래 걸렸습니다. 더 간단한 방법인 CSV 업로드와 변경 감지는 실시간 동기화보다 유지보수가 훨씬 쉽고 잘 동작합니다.

Current State

  • 69개의 소스 파일에 걸쳐 약 18,100줄의 Swift 코드
  • 16개의 판매 플랫폼 지원
  • 일일 환율을 적용한 4가지 통화
  • 4개의 데이터베이스 마이그레이션
  • 8페이지 구성의 온보딩 워크스루
  • App Store에서 무료 제공, 광고 없음

전체 시스템은 서버 비용이 전혀 들지 않습니다: 웹사이트는 GitHub Pages, CI/CD는 GitHub Actions, 배포는 Apple Developer 계정만 사용합니다. 그게 전부입니다.

If You’re Curious

아키텍처, GRDB 설정, 혹은 Claude Code 워크플로우에 대해 궁금한 점이 있으면 언제든 질문해 주세요.

0 조회
Back to Blog

관련 글

더 보기 »

모던 SQLite: 당신이 몰랐던 기능들

JSON 데이터를 다루기 위해 SQLite는 JSON 확장을 제공하여 JSON 문서를 테이블에 직접 저장하고 쿼리할 수 있게 합니다. 스키마를 유연하게 유지할 수 있습니다.