diff --git a/myApp/HaruDanim/Shared/DataStore.swift b/myApp/HaruDanim/Shared/DataStore.swift index 926ebc7..d3d5397 100644 --- a/myApp/HaruDanim/Shared/DataStore.swift +++ b/myApp/HaruDanim/Shared/DataStore.swift @@ -64,12 +64,44 @@ nonisolated enum DataStore { /// 앱·위젯·인텐트가 공유하는 컨테이너 생성. /// CloudKit 연동은 메인 앱 프로세스에서만 — 확장은 같은 파일을 로컬 전용으로 연다. - /// 앱 본체(DataStore.shared)는 스토어 없이는 동작할 수 없어, 생성 실패 시 크래시한다. + /// + /// 실패해도 곧장 크래시하지 않는다: + /// 1) 위젯 등 다른 프로세스와의 일시적 스토어 경합(특히 스키마 마이그레이션이 필요한 + /// 업데이트 직후 첫 실행)일 수 있어 짧게 재시도하고, + /// 2) 그래도 실패하면(영구적 마이그레이션/파일 손상) 기존 스토어를 백업 이름으로 보존한 뒤 + /// 새 스토어로 시작한다 — 예전에는 여기서 바로 fatalError라 스플래시도 못 띄우고 + /// 앱이 무한히 죽는 상태가 될 수 있었다. 백업 파일(HaruDanim.store.backup-*)은 남는다. static func makeContainer() -> ModelContainer { do { return try makeContainerThrowing() } catch { - fatalError("[DataStore] 컨테이너 생성 실패: \(error)") + print("[DataStore] 컨테이너 생성 실패, 재시도: \(error)") + for attempt in 1...3 { + Thread.sleep(forTimeInterval: 0.4 * Double(attempt)) + if let container = try? makeContainerThrowing() { + return container + } + } + print("[DataStore] 재시도 실패 — 스토어를 백업하고 새로 시작: \(error)") + backupBrokenStore() + do { + return try makeContainerThrowing() + } catch { + fatalError("[DataStore] 컨테이너 생성 실패(백업 후 재생성도 실패): \(error)") + } + } + } + + /// 열 수 없게 된 스토어 파일(-wal/-shm 포함)을 타임스탬프 백업 이름으로 옮긴다. + /// 삭제하지 않으므로 필요 시 파일을 되돌려 데이터를 복구할 수 있다. + private static func backupBrokenStore() { + let fileManager = FileManager.default + let stamp = Int(Date.now.timeIntervalSince1970) + for suffix in ["", "-wal", "-shm"] { + let source = URL(fileURLWithPath: storeURL.path + suffix) + guard fileManager.fileExists(atPath: source.path) else { continue } + let destination = URL(fileURLWithPath: storeURL.path + ".backup-\(stamp)" + suffix) + try? fileManager.moveItem(at: source, to: destination) } }