You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
메인폰 밖에서 촬영된 사진을 더 쉽게 찾고, 분류하고, 다시 볼 수 있도록 돕는 사진 정리 보조 서비스
서브폰·카메라 등 여러 기기로 촬영한 사진이 한 라이브러리에 섞여 있으면 원하는 사진을 다시 찾기 어렵습니다. zipzip은 사용자의 사진 라이브러리를 분석해 촬영 기기·날짜·장소 기준으로 사진을 자동 인덱싱하고, 이를 바탕으로 필터링·앨범 정리·그룹 공유까지 이어지는 정리 경험을 제공하는 iOS 앱입니다.
📱 미리보기
메타데이터 수정
필터링
앨범
공유
공유 채팅
✨ 주요 기능
기능
설명
온보딩
서비스 소개 → 사진 권한 요청 → 라이브러리 스캔으로 촬영 기기 자동 감지 → 관리 대상 기기 선택
사진
전체 사진 그리드, 기기·날짜·장소·기타 조건 필터, 사진 메타데이터(촬영일시·위치) 조회 및 편집
앨범
개인 앨범 생성·정리, 즐겨찾기 앨범(기본 제공), 사진 이동/복사/삭제
공유
Apple 로그인 후 공유 그룹 생성·초대 코드 참여, 그룹 내 공유 앨범으로 사진 공유
마이페이지
등록 기기 관리, 로그아웃·회원 탈퇴
설계 방향
로컬 우선(Local-first): 사진 원본과 메타데이터 인덱스는 기기 내 SQLite에 저장하고, 서버는 공유 기능에만 관여합니다. 개인 사진 정리는 네트워크 없이 완전히 동작합니다.
오프라인 역지오코딩: 장소 라벨링에 네트워크 API(CLGeocoder) 대신 앱에 번들된 행정동/국가 GeoJSON을 이용한 point-in-polygon 판정을 사용해, 대량 사진 처리 시의 rate limit·네트워크 의존을 제거했습니다.
서버 데이터는 미러(캐시): 공유 그룹/앨범은 서버가 원본(source of truth)이며, 로컬 DB에는 조회 성능을 위한 미러 테이블만 둡니다.
STRICT 테이블·외래키·인덱스 등 SQL을 직접 제어하면서 타입 세이프한 Swift 모델 매핑 제공. 수만 장 규모 사진 인덱스 쿼리 성능을 위해 SwiftData 대신 채택
네트워킹
Alamofire 5.12
async/await 기반 요청 직렬화(serializingDecodable), Interceptor·EventMonitor 확장 지점 제공
사진 접근
PhotoKit (PHPhotoLibrary)
시스템 사진 라이브러리 접근 및 change token 기반 증분 변경 감지
인증
Sign in with Apple + Keychain
별도 회원가입 없는 낮은 진입장벽. 토큰은 Keychain에 암호화 저장
로깅
OSLog
시스템 통합 로깅, 카테고리별(네트워크·DB) 분리
코드 품질
SwiftFormat(빌드 페이즈) + SwiftLint(SPM 플러그인)
빌드 시 자동 포맷팅으로 팀 코드 스타일 강제
의존성 관리
Swift Package Manager
Xcode 통합, 별도 도구 불필요
협업
GitHub + Figma + Notion
브랜치 전략(main/dev/feat/#이슈), 태그 기반 커밋 컨벤션
배포 타깃: iOS 26, iPhone/iPad Universal
🏗️ 시스템 아키텍처
레이어 구조
MVVM을 기반으로, 데이터 접근을 Service/Repository/Store로 분리한 계층 구조입니다.
flowchart TD
V["View (SwiftUI)"] --> VM["ViewModel (@Observable)"]
VM --> S["Service / Repository / Store"]
S --> DB[("로컬 DB — SQLiteData")]
S --> NP["NetworkProvider (Alamofire)"]
S --> PK["PhotoKit — 사진 라이브러리"]
NP --> API["zipzip 서버 API"]
모델 매핑: 테이블마다 @Table 매크로 기반 레코드 구조체(PhotoRecord 등)로 타입 세이프하게 매핑
마이그레이션: DatabaseMigrator에 단계별 마이그레이션을 등록해 스키마 변경 이력 관리. 마이그레이션 실패 시 기존 스토어를 백업 후 재생성하는 복구 로직 포함
ERD
erDiagram
device ||--o{ photo : "촬영 기기"
place ||--o{ photo : "촬영 장소"
album ||--o{ album_photo : ""
photo ||--o{ album_photo : ""
shared_group ||--o{ shared_album : ""
shared_album ||--o{ shared_photo : ""
photo {
INTEGER id PK
TEXT local_identifier UK "PhotoKit 자산 ID"
TEXT content_hash
INTEGER taken_at
INTEGER added_at
INTEGER is_favorite
REAL latitude
REAL longitude
INTEGER device_id FK
INTEGER device_pending
INTEGER place_id FK
}
device {
INTEGER id PK
TEXT make
TEXT model
INTEGER is_registered
}
place {
INTEGER id PK
TEXT name
REAL latitude
REAL longitude
}
album {
INTEGER id PK
TEXT name
INTEGER created_at
INTEGER is_favorite
}
album_photo {
INTEGER id PK
INTEGER album_id FK
INTEGER photo_id FK
INTEGER added_at
}
shared_group {
TEXT id PK "서버 UUID"
TEXT name
TEXT invite_code UK
TEXT my_role
INTEGER member_count
}
shared_album {
TEXT id PK "서버 UUID"
TEXT shared_group_id FK
TEXT name
INTEGER photo_count
}
shared_photo {
TEXT id PK "서버 UUID"
TEXT shared_album_id FK
TEXT content_hash
}
Loading
🌐 네트워크 설계
레이어 구조
flowchart TD
F["Feature API (AuthAPI, ShareGroupAPI)"] --> ANP["AuthenticatedNetworkProvider<br/>토큰 부착 · 401 시 갱신 후 재시도"]
ANP --> DNP["DefaultNetworkProvider<br/>Alamofire 요청 실행 · 에러 매핑"]
DNP --> EP["APIEndpoint 프로토콜<br/>path · method · parameters · encoding"]
DNP --> LOG["NetworkLogger (EventMonitor)<br/>+ NetworkSecretRedactor"]
ANP --> SCC["SessionCredentialController<br/>Keychain 토큰 관리 · refresh"]
Loading
구성 요소
역할
APIEndpoint
엔드포인트 명세 프로토콜. 각 기능이 enum으로 path/method/parameters를 선언하면 asURLRequest()가 요청으로 변환
NetworkProvider
request<T: Decodable>(_:) async throws -> T 단일 메서드 프로토콜. 호출부는 구현체를 모른 채 사용
DefaultNetworkProvider
Alamofire 기반 구현. 상태코드 검증, 응답 디코딩, NetworkError 매핑 담당
AuthenticatedNetworkProvider
데코레이터 패턴. 요청에 Bearer 토큰을 부착하고 401 응답 시 토큰 갱신 후 1회 재시도. 갱신 불가 시 로그아웃 처리 콜백 호출
NetworkLogger
EventMonitor로 요청/응답 전 구간 로깅. NetworkSecretRedactor가 토큰 등 민감정보를 마스킹
📣 Convention
Branch
태그/#이슈번호
main ─────────────── 운영 배포본 (직접 push 금지)
dev ──────────── 통합 브랜치 / 스프린트 단위 관리
└── feat/#1 개인 기능 개발 브랜치 (이슈 ID 기반)
hotfix/#2 ──── 긴급 버그 수정 (main에서 분기)
Tag
태그
설명
예시
feat
새로운 기능 추가
feat: 로그인 기능 추가
fix
버그 수정
fix: 로그인 예외 처리 버그 수정
docs
README 등의 문서 수정
docs: API 명세 업데이트
design
UI/UX 구현
design: 랜딩 뷰 구현
refactor
기능 변경 없이 코드 내부 구조 리팩토링
refactor: 로그인 처리 로직 리팩토링
test
단위 테스트 추가, 통합 테스트 추가, 테스트 코드 수정
test: 사용자 인증 로직 테스트 케이스 추가
chore
라이브러리 버전 수정, 패키지 관리 등
chore: 의존성 버전 업데이트
comment
주석 추가 / 수정
comment: 불필요한 주석 제거
hotfix
배포된 버전에서의 급한 버그 수정
hotfix: 서버 Timezone 설정 변경
rename
파일, 클래스 등의 이름 변경
rename: UserController → AuthController 변경
remove
파일, 클래스 등의 삭제
remove: 사용하지 않는 DTO 제거
cicd
CI/CD 관련 설정
cicd: Github Actions workflow 추가
add
파일, 에셋 추가
add: 컬러 에셋 추가
Commit Message
{태그}: 작업내용
feat: 업장 상세페이지 컴포넌트 사용하도록 수정 및 관리자 조회 API 추가
About
여러 기기의 사진을 자동 분류하고 공유 앨범으로 함께 정리하는 iOS 사진 정리 서비스 zipz!p의 iOS 앱