Repository navigation
fix: 최근 검색 동시 저장 유실 — Repository를 actor로 - #366
Merged
Merged
Conversation
## 실제 버그: 최근 검색 유실 (재현 확인)
`RecentSearchRepositoryImpl.save`/`remove`가 read-modify-write인데 값 타입이라
단일 writer가 보장되지 않았다. `AppDIContainer:234`가 repository를 1회 생성해
홈(`promoteChipDestination`)과 검색(`saveTask`)이 공유하므로 동시 쓰기 경로가
실재한다.
단일 인스턴스에 4건을 동시 저장하는 테스트로 재현:
struct (수정 전): ["서울역", "홍대입구역"] 2건 유실
actor (수정 후): 4건 전부 보존
`actor`로 전환해 단일 writer를 타입으로 강제한다. `RecentSearchRepository`
프로토콜이 이미 `async throws`라 호출처 변경은 없다.
회귀 테스트는 **반드시 단일 인스턴스로** 검증한다 — actor 격리는 인스턴스
단위라, 매번 새 인스턴스를 만드는 computed property `sut`를 쓰면 수정 후에도
실패한다(실제로 그렇게 헛짚었다). 실제 앱의 공유 조건과 일치시켜야 한다.
## 잠재 버그: refreshPulled의 defer 누락
`Task.isCancelled` 경로에서 early return하면 `refreshTask`가 영구히 non-nil로
남아 진입 가드에 영원히 걸리고(당김 새로고침 영구 잠김) `onManualSyncFinished`도
불리지 않아 스피너가 멈추지 않는 구조다.
다만 `refreshTask?.cancel()`이 `deinit`에서만 호출되므로 취소 시점엔 VM이 해제
중이라 `[weak self]`가 먼저 nil이 되어 **현재 코드 경로에서는 발현되지 않는다.**
방어적 수정이자 같은 파일 `chipTapped`(:261)와의 일관성 확보다.
## 오진이었던 것
`SceneDelegate:108-109`의 UserDefaults 직접 읽기는 버그가 아니다. 어댑터
`:341-343`이 `devDismissedDefaultsKey`를 `nonisolated static`으로 의도적으로
노출하며 "MainActor(UI)에서 동기로 읽어도 어긋나지 않는다"고 명시한다.
경고 주석(`:346-347`)은 쓰기에 대한 것이고 여기는 읽기이며, `#if DEV` 한정이다.
검증: Domain 92 / AtchaData 53 / AtchaV2 20 / Home 66 / Search 26 / Settings 12
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
실제 버그: 최근 검색 유실 (재현 확인)
RecentSearchRepositoryImpl.save/remove가 read-modify-write인데 값 타입이라 단일 writer가 보장되지 않았습니다.AppDIContainer:234가 repository를 1회 생성해 홈·검색이 공유하므로 동시 쓰기 경로가 실재합니다.struct(수정 전)["서울역", "홍대입구역"]— 2건 유실actor(수정 후)actor로 전환해 단일 writer를 타입으로 강제합니다.RecentSearchRepository프로토콜이 이미async throws라 호출처 변경은 없습니다.회귀 테스트 작성 시 주의점
반드시 단일 인스턴스로 검증해야 합니다. actor 격리는 인스턴스 단위라, 매번 새 인스턴스를 만드는 computed property
sut를 쓰면 수정 후에도 실패합니다 (실제로 그렇게 헛짚었습니다). 실제 앱의 공유 조건과 일치시켜야 수정 전후가 갈립니다.잠재 버그:
refreshPulled의defer누락Task.isCancelled경로에서 early return하면refreshTask가 영구히 non-nil로 남아 진입 가드에 영원히 걸리고(당김 새로고침 영구 잠김),onManualSyncFinished도 불리지 않아 스피너가 멈추지 않는 구조입니다.다만
refreshTask?.cancel()이deinit에서만 호출되므로 취소 시점엔 VM이 해제 중이라[weak self]가 먼저 nil이 되어 현재 코드 경로에서는 발현되지 않습니다. 방어적 수정이자 같은 파일chipTapped(:261)와의 일관성 확보입니다.오진이었던 것 (변경 없음)
SceneDelegate:108-109의 UserDefaults 직접 읽기는 버그가 아니었습니다. 어댑터:341-343이devDismissedDefaultsKey를nonisolated static으로 의도적으로 노출하며 "MainActor(UI)에서 동기로 읽어도 어긋나지 않는다" 고 명시합니다. 경고 주석(:346-347)은 쓰기에 대한 것이고 여기는 읽기이며,#if DEV한정입니다.검증
Domain 92 / AtchaData 53 / AtchaV2 20 / Home 66 / Search 26 / Settings 12 — 전부 통과
base 주의: 이 PR은
feat/v2-guest-auth위에 쌓여 있습니다. #365를 먼저 머지한 뒤 이 PR의 base가 자동으로env/dev로 바뀝니다.🤖 Generated with Claude Code