fix(sync): receive CloudKit silent pushes so cross-device changes arrive while the app is open
다른 기기(아이폰↔아이패드)에서 측정을 시작/종료해도 사용 중인 기기에는 반영되지 않고, 모음 탭 새로고침 버튼을 눌러도 그대로이던 버그 수정. 앱을 나갔다 들어와야만 반영되던 증상의 원인. 원인: 푸시 엔타이틀먼트(aps-environment)는 있었지만 UIBackgroundModes에 remote-notification이 없어 CloudKit의 "데이터 바뀜" 무음 푸시가 앱에 전달되지 않았다. Core Data+CloudKit은 이 푸시로 가져오기(import)를 트리거하므로, 없으면 앱 실행/포그라운드 복귀 때만 가져온다. 새로고침 버튼은 로컬 스토어 재조회일 뿐이라 가져오기 전에는 옛 데이터만 보였다. 수정: iOS 타깃에 IOS/Info.plist 신설(UIBackgroundModes: remote-notification)하고 INFOPLIST_FILE로 지정 — 생성 Info.plist (GENERATE_INFOPLIST_FILE=YES)와 병합된다. IOS 동기화 그룹에 membershipException을 추가해 plist가 리소스로 중복 복사되지 않게 함. 검증: Debug·Store 빌드 성공, 두 빌드 산출물 Info.plist에 UIBackgroundModes=[remote-notification]과 기존 생성 키(표시 이름· Live Activity) 병합 확인, 시뮬레이터 콜드 스타트 정상. 기기 간 동기화 도착은 실기기 2대에서 확인 필요. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
This commit is contained in:
parent
74dab79065
commit
7720ed5925
@ -16,7 +16,7 @@
|
||||
| 번들 ID | `com.yechan.HaruDanim` / App Group `group.com.yechan.HaruDanim` / CloudKit `iCloud.com.yechan.HaruDanim` |
|
||||
| 수익 모델 | 부분 유료화. 무료 한도(행동 10·목표 2·목표당 다짐 3) + 프리미엄 결제 시 해제 |
|
||||
| 결제 상품 | `com.yechan.HaruDanim.premium.monthly`(월 5,000원) / `.yearly`(년 50,000원) / `.lifetime`(80,000원) — 임시 가격, App Store Connect 등록은 아직 안 됨 |
|
||||
| 남은 배포 작업 | App Store Connect 상품 3종 등록(구독 그룹 + 비소모성, 유료 앱 계약), 실기기 검증(펜슬 필기감·캘린더 권한), App Store 배포. **코드 측 잔여 2건**: ① 결제 화면(PremiumView)에 개인정보처리방침·이용약관 링크(심사 3.1.2 필수 — URL 준비되면 추가), ② Info.plist `ITSAppUsesNonExemptEncryption=NO` 등록 |
|
||||
| 남은 배포 작업 | App Store Connect 상품 3종 등록(구독 그룹 + 비소모성, 유료 앱 계약), 실기기 검증(펜슬 필기감·캘린더 권한), App Store 배포. **코드 측 잔여 2건**: ① 결제 화면(PremiumView)에 개인정보처리방침·이용약관 링크(심사 3.1.2 필수 — URL 준비되면 추가), ② `IOS/Info.plist`(파일 있음)에 `ITSAppUsesNonExemptEncryption=NO` 추가 |
|
||||
|
||||
---
|
||||
|
||||
@ -123,6 +123,7 @@ xcrun xcstringstool sync IOS/Localizable.xcstrings --stringsdata "${files[@]}"
|
||||
- 스토어 파일: App Group 컨테이너의 `HaruDanim.store` (위젯·인텐트가 같은 DB 사용). 구 샌드박스 `default.store`는 1회 이관
|
||||
- **CloudKit 미러링은 메인 앱 프로세스만**. 위젯 확장(.appex)은 같은 파일을 로컬 전용으로 열음. 확장의 쓰기는 메인 앱이 원격 변경 알림으로 받아 내보냄
|
||||
- iCloud 동기화 = 프리미엄 + `settings.cloudSync` 토글 (앱 재시작 시 적용). 실패 시 로컬 폴백
|
||||
- **원격 변경 가져오기(import)는 무음 푸시가 트리거** — `IOS/Info.plist`의 `UIBackgroundModes: remote-notification`(+`aps-environment` 엔타이틀먼트) 필수. 이 모드가 없으면 앱 실행/포그라운드 복귀 때만 가져와서, 다른 기기의 변경이 사용 중에는 절대 안 보이고 새로고침 버튼도 무용지물이 된다(수정된 버그). 가져오기를 코드로 강제하는 공식 API는 없음. iOS 타깃 Info.plist는 `GENERATE_INFOPLIST_FILE=YES` + `INFOPLIST_FILE=IOS/Info.plist` 병합 방식(파일에는 생성 설정에 없는 키만 넣음, IOS 동기화 그룹의 membershipException으로 리소스 복사 제외)
|
||||
- 컨테이너 생성 실패 시: 짧은 재시도 3회 → 스토어를 `.backup-<ts>`로 보존 후 새로 시작 (fatalError로 즉사하지 않음)
|
||||
- `ensureUniqueEntityIDs`: uuid 기본값 마이그레이션 중복 보정 (앱 시작 1회)
|
||||
|
||||
@ -135,7 +136,7 @@ xcrun xcstringstool sync IOS/Localizable.xcstrings --stringsdata "${files[@]}"
|
||||
### 5.3 변경 전파
|
||||
- 앱 내 모든 mutation 끝에 **`DataChange.commit(context:)`**: save → LiveActivity sync(장시간 측정 알림 예약 동기화 포함) → `WidgetCenter.reloadAllTimelines()` + `ControlCenter.reloadAllControls()` → 워치 스냅숏 push(350ms 디바운스 — 연타 시 마지막 상태만 집계·전송). 위젯/시리 경로는 `IntentStore.performWrite`가 같은 역할(한 컨텍스트로 조회·수정·저장 원자 처리 + 저장 1회 재시도)
|
||||
- `CloudSyncMonitor`: NSPersistentStoreRemoteChange를 2초 디바운스로 받아 위젯·워치·LiveActivity 갱신 (화면은 건드리지 않음)
|
||||
- `AppRefresh`(모음 탭 새로고침 버튼): 루트 뷰 `.id(token)` 리셋으로 모든 @Query 재조회 — 다른 기기 변경이 화면에 안 보일 때의 수동 해결책
|
||||
- `AppRefresh`(모음 탭 새로고침 버튼): 루트 뷰 `.id(token)` 리셋으로 모든 @Query 재조회 — 다른 기기 변경이 화면에 안 보일 때의 수동 해결책. **로컬 스토어에 이미 도착한 변경만 반영** — 가져오기 자체는 무음 푸시가 담당(§5.1)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -111,6 +111,13 @@
|
||||
);
|
||||
target = 725A9E832FFCC65000846FFB /* Haru_Danim */;
|
||||
};
|
||||
AA0000000000000000000013 /* Exceptions for "IOS" folder in "Haru_Danim" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = 725A9E832FFCC65000846FFB /* Haru_Danim */;
|
||||
};
|
||||
AA0000000000000000000012 /* Exceptions for "Haru_DanimWatchWidgets" folder in "Haru_DanimWatch Watch App" target */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
@ -127,6 +134,9 @@
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
725A9E862FFCC65000846FFB /* IOS */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
exceptions = (
|
||||
AA0000000000000000000013 /* Exceptions for "IOS" folder in "Haru_Danim" target */,
|
||||
);
|
||||
path = IOS;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -599,6 +609,7 @@
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = IOS/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "하루 다님";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle";
|
||||
INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "일기 타임테이블에 캘린더 일정을 함께 표시하기 위해 필요해요.";
|
||||
@ -640,6 +651,7 @@
|
||||
DEVELOPMENT_TEAM = 5GNS4Z2SYT;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = IOS/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "하루 다님";
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle";
|
||||
INFOPLIST_KEY_NSCalendarsFullAccessUsageDescription = "일기 타임테이블에 캘린더 일정을 함께 표시하기 위해 필요해요.";
|
||||
|
||||
@ -8,6 +8,10 @@
|
||||
// 다시 조회된다 (재시작과 같은 효과, 데이터는 그대로).
|
||||
// - 원격 변경 자동 반영(CloudSyncMonitor)은 위젯·워치·라이브 액티비티만 갱신한다.
|
||||
// 화면 전체 리셋은 입력 중인 시트를 닫아버릴 수 있어 수동 버튼으로만 한다.
|
||||
// ⚠️ 이 버튼은 "로컬 스토어에 이미 도착한" 변경을 화면에 반영할 뿐, CloudKit 가져오기를
|
||||
// 강제할 공식 API는 없다. 가져오기 자체는 무음 푸시(UIBackgroundModes:
|
||||
// remote-notification, IOS/Info.plist)가 트리거한다 — 이 모드가 빠져 있으면 앱
|
||||
// 실행/포그라운드 복귀 때만 가져와서 새로고침을 눌러도 옛 데이터가 보인다(수정된 버그).
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
14
myApp/HaruDanim/IOS/Info.plist
Normal file
14
myApp/HaruDanim/IOS/Info.plist
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- CloudKit 무음 푸시 수신용 — 다른 기기의 변경을 즉시 가져오기(import) 위해 필수.
|
||||
이 모드가 없으면 앱 실행/포그라운드 복귀 때만 가져오기가 일어나
|
||||
iPad·iPhone 간 측정 시작/종료가 화면·새로고침에 반영되지 않았다.
|
||||
나머지 Info.plist 키는 GENERATE_INFOPLIST_FILE=YES가 생성해 이 파일과 병합한다. -->
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
Loading…
x
Reference in New Issue
Block a user