WidgetKit을 위한 snapshot 저장 구조
앱이 만든 표시값을 App Group snapshot에 저장해 WidgetKit이 먼저 읽게 한 구조
한강자리 위젯은 앱과 별도로 실행되고 다른 주기로 갱신된다. WidgetKit extension은 앱의 메모리를 공유하지 않으므로 바로 읽을 수 있는 표시값을 App Group snapshot에 따로 저장했다.
위젯을 위한 별도 표시값
WidgetKit extension은 App Group에 저장한 표시값을 먼저 읽고 필요할 때 네트워크를 시도한다.
한강자리 위젯은 다음을 해야 했다.
- 앱을 열지 않아도 최근 값을 보여준다.
- 네트워크가 실패해도 마지막으로 확인한 값을 버리지 않는다.
- 주차 위젯과 일반 공원 위젯에 용도별 정보를 제공한다.
- 위젯을 탭하면 앱의 해당 공원과 화면으로 이어진다.
- 오래된 정보에는 stale 상태를 표시한다.
메인 앱의 메모리는 extension의 독립 실행 시점에 사용할 수 없다. 앱과 위젯 사이의 공유 저장소로 App Group snapshot을 두었다.
flowchart LR App["메인 앱"] --> Repo["저장소"] Repo --> SwiftData["SwiftData 캐시"] App --> Builder["스냅샷 빌더"] Builder --> AppGroup["App Group 스냅샷"] Widget["WidgetKit 확장"] --> Loader["위젯 항목 로더"] Loader --> AppGroup Loader --> API["/v2 home-summary<br/>상태 대체"] Loader --> Entry["위젯 표시 항목"]
snapshot에 남긴 표시값
snapshot은 위젯이 실제로 표시할 값만 남긴 작은 묶음이다.
주차 snapshot에는 공원 ID, 짧은 공원명, 주차장 목록, 잔여 대수, 총면수, 여유 정도, 확인한 시각, stale로 볼 시간, 길찾기 URL 같은 값이 들어간다. 일반 공원 snapshot에는 혼잡도, 날씨, 미세먼지, 주요 신호와 예측 변화가 들어간다.
주차 위젯과 일반 위젯은 같은 공원에서도 서로 다른 값을 표시하므로 snapshot key를 용도별로 나눈다.
| Snapshot | 판단 기준 | 저장 단위 |
|---|---|---|
| Parking snapshot | 지금 차를 가져가도 되는가 | 공원 + 표시 방식 |
| General snapshot | 오늘 이 공원에 가도 되는가 | 공원 + 일반 focus |
| Legacy snapshot | 이전 버전 호환 | 단일 주차 snapshot |
용도별 key는 위젯 설정을 바꿀 때 다른 snapshot을 덮어쓰는 일을 막는다. snapshot 필드도 각 위젯의 판단 기준에 맞췄다.
저장값을 먼저 읽는 위젯
위젯 entry loader는 다음 순서로 움직인다.
sequenceDiagram
autonumber
participant Widget as 위젯 프로바이더
participant Loader as 위젯 항목 로더
participant Snapshot as App Group 스냅샷
participant API as 한강자리 API
participant Builder as 스냅샷 빌더
Widget->>Loader: 타임라인에 넣을 값 요청
Loader->>Snapshot: 용도에 맞는 저장값 읽기
alt 스냅샷이 최신이고 강제 갱신 아님
Snapshot-->>Loader: 쓸 수 있는 저장값 반환
Loader-->>Widget: 저장값으로 위젯 항목 반환
else 오래됐거나 없음
Loader->>API: 첫 화면 또는 상태 데이터 요청
API-->>Loader: 화면용 읽기 모델 반환
Loader->>Builder: 위젯에 보여줄 값 만들기
Builder-->>Snapshot: 공유 영역에 표시값 저장
Loader-->>Widget: 새 응답으로 위젯 항목 반환
end
snapshot 저장 시각과 내부 데이터의 observed_at은 서로 다른 최신성을 나타낸다.
- snapshot 최신성: 위젯 entry를 다시 만들 필요가 있는가.
- data 최신성: 표시 중인 데이터가 최신이라고 말할 만큼 충분한가.
위젯이 cache hit로 빠르게 열리더라도 내부 주차 값이 stale이면 UI는 오래됐다고 보여줘야 한다.
SwiftData와 App Group의 역할
SwiftData는 앱 내부의 read cache다. 공원, 주차장, status, forecast, home summary를 앱 화면에서 재사용하게 해준다.
App Group snapshot은 위젯과 공유하는 작은 표시값 묶음이다. extension이 안정적으로 읽을 수 있어야 하므로 화면에 필요한 값만 Codable payload로 저장한다.
이 역할 구분은 위젯을 앱 내부 데이터 변경에서 분리한다. snapshot은 앱 cache와 중복된 저장소가 되지 않도록 “위젯 entry를 만들기 위한 최종 표시 재료”로 제한했다.
실패와 대체 표시 상태
위젯은 실패 상태에 따라 마지막 확인값, unavailable 또는 placeholder를 표시한다.
| 상황 | 보여주는 방식 |
|---|---|
| fresh snapshot 있음 | snapshot으로 entry 만들기 |
| snapshot은 있지만 오래됨 | 네트워크 시도 후 실패하면 stale 표현과 함께 fallback |
| snapshot 없음 | API 시도 후 실패하면 unavailable 또는 placeholder |
| 가까운 공원 선택에서 위치 불가 | 위치 불가 이유로 unavailable |
| API payload 공원 불일치 | 빈 데이터로 다룸 |
위젯은 “정보 없음”, “마지막으로 확인한 정보”와 “방금 갱신한 정보”를 서로 다른 상태로 표시한다. 숫자 하나만 보더라도 stale 여부를 확인할 수 있게 했다.
작은 화면에 표시하는 최신성
App Group snapshot은 앱과 WidgetKit extension이 공유하는 표시 형식이다. 주차와 일반 공원 위젯에는 용도별 값 묶음을 남기고 snapshot 저장 시각과 내부 데이터의 observed_at을 따로 판정한다. fallback은 마지막 확인값과 정보 없음을 구분하며 snapshot에는 표시와 deep link에 필요한 값만 저장한다.
Comments
아직 댓글이 없습니다. 첫 댓글을 남겨주세요.
검토 대기 중