Skip to content

[Refactor] Token 관리 Keychain + Actor 전환#29

Merged
ddodle merged 5 commits into
mainfrom
refactor/keychain-actor
May 31, 2026
Merged

[Refactor] Token 관리 Keychain + Actor 전환#29
ddodle merged 5 commits into
mainfrom
refactor/keychain-actor

Conversation

@ddodle

@ddodle ddodle commented May 31, 2026

Copy link
Copy Markdown
Collaborator

✨ PR 유형

어떤 변경 사항이 있나요??

  • 새로운 기능 추가
  • 버그 수정
  • 사용자 UI 디자인 변경 및 추가
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 테스트 추가, 테스트 리팩토링
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

🛠️ 작업내용

아키텍처 변경

Before: Repository → MoyaProvider(+AuthPlugin) → Alamofire → 서버
After:  Repository → MoyaNetworkAdapter → NetworkClient(actor) → URLSession → 서버

신규 파일 (9개)

  • NetworkClient (actor): 토큰 주입, 401 감지→리프레시→재시도, Task deduplication
  • KeychainTokenStore (actor): Keychain SecItem API로 토큰 CRUD (UserDefaults 대체)
  • MoyaNetworkAdapter: Moya TargetType → URLRequest 변환, NetworkClient에 위임
  • TokenRefreshServiceImpl: /auth/refresh 직접 호출 (순수 URLSession)
  • AuthSystemFactory: NetworkClient 의존성 조립 팩토리
  • TokenStoreProtocol: TokenStore, TokenRefreshService, AuthenticationPolicy 프로토콜
  • TokenPair: 토큰 쌍 값 타입 (Sendable, nonisolated let)
  • DefaultAuthenticationPolicy: 경로 기반 인증 정책
  • NetworkError: 네트워크 에러 enum

수정 파일

  • API Targets: headers에서 UserDefaults 토큰 접근 삭제 → NetworkClient가 Bearer 주입
  • 전체 Repository (Photo, Tag, Description, Search, Album): MoyaProvider → MoyaNetworkAdapter
  • UserRepository: plainProvider + authedProvider + tokenStore → adapter + networkClient
  • UserRepositoryProtocol: var hasTokens: Boolfunc hasTokens() async -> Bool
  • LoginViewModel: init의 hasTokens 체크 → onAppear로 이동 (async 대응)
  • AppContainer: MoyaProvider 6개 + AuthPlugin + AuthedProvider → networkClient + adapter 2개로 통합

삭제 파일 (5개)

  • TokenStore.swift, AuthPlugin.swift, AuthProvider.swift
  • MoyaProvider+Async.swift, AuthedProvider+Async.swift

기타

  • DEBUG 빌드 시 로그인 바이패스 + Mock Provider DI 등록
  • TokenPerformanceTests를 KeychainTokenStore async API에 맞게 전환

📋 추후 진행 상황

  • Step 3: Swift Concurrency 전면 전환 (TaskGroup 병렬 로딩, @mainactor 격리 등)

📌 리뷰 포인트

  • NetworkClient actor의 Task deduplication 패턴 (refreshToken 중복 방지)
  • Actor reentrancy: refreshTask = taskawait task.value 이전에 저장하는 순서
  • MoyaNetworkAdapter의 TargetType → URLRequest 변환 로직 (multipart 포함)
  • KeychainTokenStore의 delete → add 패턴 vs update 패턴

✅ Checklist

  • 커밋 메시지 컨벤션에 맞게 작성했습니다
  • 유지-보수를 위해 주석처리를 잘 작성하였는가?

Closes #28

Summary by CodeRabbit

  • New Features

    • 개선된 네트워크 계층: 자동 토큰 주입·401 재시도·단일 리프레시 공유
    • 키체인 기반 안전한 토큰 저장 및 자동 갱신
  • Refactoring

    • 모든 API 호출을 새 네트워크 어댑터로 이전
    • 토큰 상태 확인이 동기 → 비동기 방식으로 변경
    • 인증 헤더 관리 중앙화
  • Behavior

    • 디버그 빌드에서 로그인 화면을 건너뛰고 바로 앱 진입
  • Tests

    • Keychain 기반 토큰 성능 테스트로 갱신 및 비동기화 반영

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a82423fb-e1ba-4151-8332-c77ea8afddd7

📥 Commits

Reviewing files that changed from the base of the PR and between ac78d05 and 37364eb.

📒 Files selected for processing (4)
  • Rephoto_iOS/Core/NetworkAdapter/NetworkClient/NetworkClient.swift
  • Rephoto_iOS/Core/NetworkAdapter/NetworkClient/TokenPair.swift
  • Rephoto_iOS/Core/NetworkAdapter/TokenRefreshService/MoyaNetworkAdapter.swift
  • Rephoto_iOS/Utilities/Keychain/KeychainTokenStore.swift
🚧 Files skipped from review as they are similar to previous changes (3)
  • Rephoto_iOS/Core/NetworkAdapter/NetworkClient/NetworkClient.swift
  • Rephoto_iOS/Utilities/Keychain/KeychainTokenStore.swift
  • Rephoto_iOS/Core/NetworkAdapter/TokenRefreshService/MoyaNetworkAdapter.swift

Walkthrough

이 PR은 Keychain 기반 비동기 TokenStore와 NetworkClient actor, MoyaNetworkAdapter, TokenRefreshService를 도입하고 기존 Moya Provider/AuthPlugin/동기 토큰 저장소를 제거하여 네트워크·인증 스택과 DI를 재구성합니다.

변경 사항

네트워크 및 토큰 관리 리팩토링

Layer / File(s) 요약
프로토콜 및 기본 타입 정의
Rephoto_iOS/Core/NetworkAdapter/NetworkClient/TokenStoreProtocol.swift, Rephoto_iOS/Core/Error/NetworkError.swift, Rephoto_iOS/Core/NetworkAdapter/NetworkClient/TokenPair.swift, Rephoto_iOS/Core/NetworkAdapter/NetworkClient/DefaultAuthenticationPolicy.swift
TokenStore/TokenRefreshService/AuthenticationPolicy 프로토콜과 NetworkError, TokenPair, DefaultAuthenticationPolicy를 추가합니다.
Keychain 기반 토큰 저장소 구현
Rephoto_iOS/Utilities/Keychain/KeychainTokenStore.swift
actor KeychainTokenStore 구현: Keychain CRUD, 인코딩/디코딩, 에러(KeychainError) 처리.
토큰 갱신 서비스
Rephoto_iOS/Core/NetworkAdapter/TokenRefreshService/TokenRefreshServiceImpl.swift
/auth/refresh 호출로 TokenPair 갱신 구현, 요청/응답 DTO와 TokenRefreshError 정의.
NetworkClient Actor: 토큰 주입 및 401 처리
Rephoto_iOS/Core/NetworkAdapter/NetworkClient/NetworkClient.swift
요청에 Authorization 헤더 주입, 401 감지 시 refresh 및 재시도, refresh Task deduplication, save/logout/isLoggedIn/setOnRefreshFailed API 제공.
MoyaNetworkAdapter: Moya 통합
Rephoto_iOS/Core/NetworkAdapter/TokenRefreshService/MoyaNetworkAdapter.swift
TargetType → URLRequest 변환, multipart·parameter 인코딩, NetworkClient에 위임, DEBUG 로깅, Moya.Response 구성.
DI 구성: AuthSystemFactory 및 AppContainer
Rephoto_iOS/Core/NetworkAdapter/AuthSystemFactory.swift, Rephoto_iOS/Core/DIContainer/AppContainer.swift
AuthSystemFactory로 NetworkClient 조립, AppContainer에서 networkClient와 MoyaNetworkAdapter를 생성해 리포지토리에 주입; DEBUG에서 Mock UseCaseProvider 등록 추가.
Repository 마이그레이션: MoyaProvider → MoyaNetworkAdapter
Rephoto_iOS/Features/*/Data/Repositories/*.swift
Photo, Tag, Description, Search, Album, User 리포지토리를 adapter 기반 호출로 전환하고 DTO 디코딩 및 도메인 매핑 흐름은 유지합니다.
API Target 정리: 토큰 헤더 주입 제거
Rephoto_iOS/Network/APITargetType.swift, Rephoto_iOS/Features/*/Data/Targets/*.swift
APITargetType에서 UserDefaults 기반 Authorization 헤더 주입 제거, PhotosAPITarget headers 단순화, Alamofire import를 internal로 변경.
Protocol 및 UseCase 계층 업데이트
Rephoto_iOS/Features/User/Domain/Interfaces/UserRepositoryProtocol.swift, Rephoto_iOS/Features/User/Presentation/...
UserRepositoryProtocol의 hasTokens를 동기 프로퍼티에서 async 메서드로 변경하고 UseCase/Mock을 동기화합니다.
UI 계층 업데이트: LoginViewModel 및 ContentView
Rephoto_iOS/Features/User/Presentation/ViewModels/LoginViewModel.swift, Rephoto_iOS/App/ContentView.swift
LoginViewModel은 onAppear에서 비동기 hasTokens 검사로 로그인 상태를 결정하고 setOnRefreshFailed를 등록합니다. ContentView는 DEBUG 빌드에서 로그인 체크를 우회하여 탭뷰를 표시합니다.
테스트 업데이트: TokenPerformanceTests
Rephoto_iOSTests/TokenPerformanceTests.swift
KeychainTokenStore를 사용하도록 전환, 모든 테스트를 async/throws로 변경하고 비동기 반복 측정으로 재구성합니다.
기존 코드 제거
Rephoto_iOS/Features/User/Auth/*, Rephoto_iOS/Core/NetworkAdapter/*+Async.swift
기존 AuthPlugin, AuthedProvider, UserDefaults 기반 TokenStore 및 Moya async 래퍼 파일들을 제거/대체했습니다.

Sequence Diagram

sequenceDiagram
  participant App as App/UI
  participant NC as NetworkClient(actor)
  participant Policy as DefaultAuthenticationPolicy
  participant KVS as KeychainTokenStore(actor)
  participant RS as TokenRefreshServiceImpl
  participant S as URLSession

  App->>NC: request(URLRequest)
  NC->>Policy: requireAuthentication(request)
  NC->>KVS: getAccessToken()
  KVS-->>NC: accessToken
  NC->>S: data(for: request with Authorization)
  S-->>NC: (Data, HTTPURLResponse)
  NC->>Policy: isUnauthorizedResponse(response)
  alt unauthorized
    NC->>KVS: getRefreshToken()
    KVS-->>NC: refreshToken
    NC->>RS: refresh(refreshToken)
    RS-->>NC: TokenPair
    NC->>KVS: save(access, refresh)
    NC->>S: retry original request
  end
  NC-->>App: (Data, HTTPURLResponse) or throw NetworkError
Loading

예상 코드 리뷰 노력

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Grit-23/Rephoto_Frontend_iOS#12: TokenPerformanceTests 관련 변경(동기→비동기 Keychain 테스트)으로 테스트 벤치마크가 겹칩니다.
  • Grit-23/Rephoto_Frontend_iOS#14: AppContainer 인증/DI 변경과 관련된 PR로, DI/인증 구성 충돌 또는 중복 수정 가능성이 있습니다.

"나는 토끼, 키체인에 숨긴 당근처럼,
액터로 경주를 멈추고,
토큰은 안전히, 네트워크는 다시 춤추네.
디버그에선 탭을 열어두고, 테스트는 재빠르게,
깡총깡총 리팩토리 축하해!" 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목 '[Refactor] Token 관리 Keychain + Actor 전환'은 변경사항의 핵심(토큰 관리 리팩토링)을 명확하게 요약하고 있습니다.
Description check ✅ Passed PR 설명이 PR 유형(리팩토링, 테스트 리팩토링, 파일 삭제) 체크, 작업내용(아키텍처 변경, 신규/수정/삭제 파일), 추후 진행상황, 리뷰 포인트, 체크리스트를 포함하여 템플릿 요구사항을 충족합니다.
Linked Issues check ✅ Passed 모든 코드 변경사항이 #28 이슈의 목표를 충족합니다: KeychainTokenStore actor 추가, NetworkClient actor 구현, MoyaNetworkAdapter 도입, 토큰 저장소 프로토콜/정책/에러 정의, 저장소 의존성 통합, async hasTokens() 변경, DEBUG 로그인 바이패스 및 Mock DI 등록 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 #28 이슈의 범위 내 토큰 관리 리팩토링과 직접적으로 관련되어 있으며, 범위 밖의 변경사항은 없습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/keychain-actor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ddodle ddodle self-assigned this May 31, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (1)
Rephoto_iOS/App/ContentView.swift (1)

19-29: 💤 Low value

DEBUG 로그인 우회가 모든 디버그 빌드에서 로그인 화면을 비활성화합니다.

#if DEBUGif true는 디버그 빌드에서 항상 RephotoTabView()를 표시하므로 LoginView와 이번 PR에서 추가된 await provider.hasTokens() 기반 로그인 흐름을 디버그에서 전혀 확인할 수 없게 됩니다. 또한 하드코딩된 if true는 컴파일러 경고/코드 스멜을 유발합니다. 환경 변수나 launch argument 같은 토글로 우회 여부를 제어하면 디버그에서도 로그인 흐름 테스트가 가능합니다.

런치 인자나 환경 변수 기반 토글로 전환하는 구현을 작성해 드릴까요?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Rephoto_iOS/App/ContentView.swift` around lines 19 - 29, The debug-only
bypass uses `#if DEBUG` with `if true`, which always shows `RephotoTabView()`
and prevents testing the `LoginView` and the new `await provider.hasTokens()`
login flow; replace the hardcoded `if true` with a controllable toggle (read a
launch argument or environment variable) so in debug builds you can opt-in/out
of the bypass; modify the conditional around
`RephotoTabView()`/`LoginView(loginVM:)` to consult that toggle (e.g., check
ProcessInfo.processInfo.environment or CommandLine.arguments) and fall back to
`loginVM.isLoggedIn` and `await provider.hasTokens()` logic when the toggle is
off, removing the `if true` to eliminate the warning.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Rephoto_iOS/Core/DIContainer/AppContainer.swift`:
- Around line 85-90: The autoRegister() method in AppContainer is registering
Mock*UseCaseProvider for every DEBUG build, which overrides real implementations
too broadly. Narrow the registration scope by using a dedicated flag or
Preview/Test-only condition inside autoRegister(), so homeUseCaseProvider,
searchUseCaseProvider, and userUseCaseProvider only resolve to mock providers in
those targeted cases and not during normal debug runs.

In `@Rephoto_iOS/Core/NetworkAdapter/NetworkClient/NetworkClient.swift`:
- Around line 99-112: 현재 refreshToken()가 네트워크/서버 일시적 오류로 실패해도 catch에서 무조건
onRefreshFailed?()를 호출하고 NetworkError.unauthorized를 던져 강제 로그아웃을 유발합니다; 수정 방법은
performRequest의 catch에서 잡은 error의 타입/상태를 판별해 인증 실패로 명확히 판단되는 경우(예:
refreshToken()이 반환하는 TokenRefreshError.serverError로 HTTP 401 응답을 포함하거나 refresh
경로에서 authPolicy가 인증 실패를 명시할 때)에만 onRefreshFailed?()를 호출하고
NetworkError.unauthorized를 던지며, 그렇지 않은 일시적 오류는 원래 에러(또는 네트워크/서버 에러로 적절히 래핑한 에러)를
다시 throw하거나 재시도 로직으로 넘기도록 변경하세요; 관련 식별자: authPolicy.isUnauthorizedResponse(_:),
refreshToken(), onRefreshFailed?(), NetworkError.unauthorized,
TokenRefreshError.serverError.

In `@Rephoto_iOS/Core/NetworkAdapter/NetworkClient/TokenPair.swift`:
- Around line 10-19: Remove the unnecessary nonisolated annotations and the
explicit memberwise initializer from the TokenPair struct: delete the
nonisolated keywords on accessToken and refreshToken and remove the custom
init(accessToken:refreshToken:) so the compiler-synthesized memberwise
initializer is used; keep TokenPair as a plain struct with the two stored
properties (accessToken, refreshToken).

In
`@Rephoto_iOS/Core/NetworkAdapter/TokenRefreshService/MoyaNetworkAdapter.swift`:
- Around line 17-21: The injected baseURL property in MoyaNetworkAdapter is
currently not used when creating request URLs, which causes the actual requests
to always use target.baseURL, ignoring the injected value. Fix this by modifying
the request construction logic (likely in methods around lines 17-21 and 90-93)
to build URLs starting from self.baseURL instead of target.baseURL, ensuring the
injected baseURL is respected and allows environment or test host switching.

In
`@Rephoto_iOS/Core/NetworkAdapter/TokenRefreshService/TokenRefreshServiceImpl.swift`:
- Line 27: The refresh request is currently sending the token in the request
body under a capitalized key (RefreshTokenRequestBody(Authorization:
refreshToken)) which likely mismatches the server contract; inspect and confirm
the server's expected field name (e.g. "authorization" vs "Authorization" or
whether the token should be in the Authorization header) and then update
TokenRefreshServiceImpl and the RefreshTokenRequestBody model so the serialized
request.httpBody uses the exact field name the server expects (or move the token
to request.setValue("Bearer <token>", forHTTPHeaderField: "Authorization") if
the API requires a header); ensure the encoder/serialization used for
request.httpBody reflects the corrected property name.

In `@Rephoto_iOS/Features/Home/Data/Target/DescriptionAPITarget.swift`:
- Line 10: The file contains the nonstandard declaration "internal import
Alamofire" which is not compatible with Swift 5/older language modes and appears
unnecessary in DescriptionAPITarget.swift (no Alamofire usage); change it to a
regular "import Alamofire" only if the target actually uses Alamofire, otherwise
remove the import entirely; also audit the same pattern in PhotosAPITarget,
UserAPITarget, TagAPITarget, SearchAPITarget, AlbumAPITarget and
MoyaNetworkAdapter and replace "internal import Alamofire" with either a plain
"import Alamofire" when needed or delete the import when unused to restore Swift
5 compatibility.

In `@Rephoto_iOS/Features/Home/Data/Target/TagAPITarget.swift`:
- Line 10: The import line using an access modifier "internal import Alamofire"
in TagAPITarget.swift should be fixed because Swift 5 does not support
AccessLevelOnImport by default; either remove the access modifier to use "import
Alamofire" in TagAPITarget.swift, or update the project's Swift language version
to 6, or add the compiler flag -enable-experimental-feature AccessLevelOnImport
to the project's Other Swift Flags; locate and update the offending "internal
import Alamofire" statement in TagAPITarget.swift accordingly.

In `@Rephoto_iOS/Features/User/Data/Repositories/UserRepository.swift`:
- Around line 50-52: The setOnRefreshFailed registration in UserRepository
currently wraps networkClient.setOnRefreshFailed in _Concurrency.Task which does
not guarantee registration completion; change UserRepository.setOnRefreshFailed
to be async and directly await networkClient.setOnRefreshFailed so the callback
is registered before returning (update the method signature in UserRepository
from func setOnRefreshFailed(_:) to async func setOnRefreshFailed(_:) and remove
the Task wrapper), then propagate that signature change through
UserRepositoryProtocol and UserUseCaseProviderProtocol and update call sites
such as LoginViewModel to await userRepository.setOnRefreshFailed(...) (or adapt
initialization flow) so registration is guaranteed before any refresh can occur.

In `@Rephoto_iOS/Features/User/Data/Targets/UserAPITarget.swift`:
- Line 10: The import line uses the Swift-6-only syntax "internal import
Alamofire" which is not stable on older toolchains; change it to a plain module
import ("import Alamofire") or, if you must keep the access-on-import form,
guard it with a Swift version conditional so the code uses "internal import
Alamofire" only when compiling with Swift 6+ and falls back to "import
Alamofire" for earlier versions—update the import statement in
UserAPITarget.swift accordingly.

In `@Rephoto_iOS/Utilities/Keychain/KeychainTokenStore.swift`:
- Around line 31-34: The save(accessToken:refreshToken:) method can leave
Keychain in an inconsistent state if saving refreshToken fails after accessToken
succeeded; modify save(accessToken:refreshToken:) so it attempts to save
accessToken via saveToKeychain(key: accessTokenKey, value: accessToken) and then
save refreshToken, and if the second save throws perform a rollback by deleting
the previously saved accessToken (call deleteFromKeychain(key: accessTokenKey)
or an existing remove method) before rethrowing the original error; ensure both
async throws are awaited and propagate the correct error (if delete also fails,
combine or prefer the refreshToken save error) so callers receive failure and
Keychain remains consistent.

---

Nitpick comments:
In `@Rephoto_iOS/App/ContentView.swift`:
- Around line 19-29: The debug-only bypass uses `#if DEBUG` with `if true`,
which always shows `RephotoTabView()` and prevents testing the `LoginView` and
the new `await provider.hasTokens()` login flow; replace the hardcoded `if true`
with a controllable toggle (read a launch argument or environment variable) so
in debug builds you can opt-in/out of the bypass; modify the conditional around
`RephotoTabView()`/`LoginView(loginVM:)` to consult that toggle (e.g., check
ProcessInfo.processInfo.environment or CommandLine.arguments) and fall back to
`loginVM.isLoggedIn` and `await provider.hasTokens()` logic when the toggle is
off, removing the `if true` to eliminate the warning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 22f1b5ab-3ac7-4372-9746-6d401c3f79b9

📥 Commits

Reviewing files that changed from the base of the PR and between 501eadc and ac78d05.

📒 Files selected for processing (34)
  • Rephoto_iOS/App/ContentView.swift
  • Rephoto_iOS/Core/DIContainer/AppContainer.swift
  • Rephoto_iOS/Core/Error/NetworkError.swift
  • Rephoto_iOS/Core/NetworkAdapter/AuthSystemFactory.swift
  • Rephoto_iOS/Core/NetworkAdapter/AuthedProvider+Async.swift
  • Rephoto_iOS/Core/NetworkAdapter/MoyaProvider+Async.swift
  • Rephoto_iOS/Core/NetworkAdapter/NetworkClient/DefaultAuthenticationPolicy.swift
  • Rephoto_iOS/Core/NetworkAdapter/NetworkClient/NetworkClient.swift
  • Rephoto_iOS/Core/NetworkAdapter/NetworkClient/TokenPair.swift
  • Rephoto_iOS/Core/NetworkAdapter/NetworkClient/TokenStoreProtocol.swift
  • Rephoto_iOS/Core/NetworkAdapter/TokenRefreshService/MoyaNetworkAdapter.swift
  • Rephoto_iOS/Core/NetworkAdapter/TokenRefreshService/TokenRefreshServiceImpl.swift
  • Rephoto_iOS/Features/Home/Data/Repositories/DescriptionRepository.swift
  • Rephoto_iOS/Features/Home/Data/Repositories/PhotoRepository.swift
  • Rephoto_iOS/Features/Home/Data/Repositories/TagRepository.swift
  • Rephoto_iOS/Features/Home/Data/Target/DescriptionAPITarget.swift
  • Rephoto_iOS/Features/Home/Data/Target/PhotosAPITarget.swift
  • Rephoto_iOS/Features/Home/Data/Target/TagAPITarget.swift
  • Rephoto_iOS/Features/Search/Data/Repositories/AlbumRepository.swift
  • Rephoto_iOS/Features/Search/Data/Repositories/SearchRepository.swift
  • Rephoto_iOS/Features/Search/Data/Targets/AlbumAPITarget.swift
  • Rephoto_iOS/Features/Search/Data/Targets/SearchAPITarget.swift
  • Rephoto_iOS/Features/User/Auth/AuthPlugin.swift
  • Rephoto_iOS/Features/User/Auth/AuthProvider.swift
  • Rephoto_iOS/Features/User/Auth/TokenStore.swift
  • Rephoto_iOS/Features/User/Data/Repositories/UserRepository.swift
  • Rephoto_iOS/Features/User/Data/Targets/UserAPITarget.swift
  • Rephoto_iOS/Features/User/Domain/Interfaces/UserRepositoryProtocol.swift
  • Rephoto_iOS/Features/User/Presentation/Preview/MockUserUseCaseProvider.swift
  • Rephoto_iOS/Features/User/Presentation/Provider/UserUseCaseProvider.swift
  • Rephoto_iOS/Features/User/Presentation/ViewModels/LoginViewModel.swift
  • Rephoto_iOS/Network/APITargetType.swift
  • Rephoto_iOS/Utilities/Keychain/KeychainTokenStore.swift
  • Rephoto_iOSTests/TokenPerformanceTests.swift
💤 Files with no reviewable changes (5)
  • Rephoto_iOS/Core/NetworkAdapter/MoyaProvider+Async.swift
  • Rephoto_iOS/Features/User/Auth/AuthPlugin.swift
  • Rephoto_iOS/Core/NetworkAdapter/AuthedProvider+Async.swift
  • Rephoto_iOS/Features/User/Auth/AuthProvider.swift
  • Rephoto_iOS/Features/User/Auth/TokenStore.swift

Comment thread Rephoto_iOS/Core/DIContainer/AppContainer.swift
Comment thread Rephoto_iOS/Core/NetworkAdapter/NetworkClient/NetworkClient.swift
Comment thread Rephoto_iOS/Core/NetworkAdapter/NetworkClient/TokenPair.swift
Comment thread Rephoto_iOS/Features/Home/Data/Target/DescriptionAPITarget.swift
Comment thread Rephoto_iOS/Features/Home/Data/Target/TagAPITarget.swift
Comment thread Rephoto_iOS/Features/User/Data/Repositories/UserRepository.swift
Comment thread Rephoto_iOS/Features/User/Data/Targets/UserAPITarget.swift
Comment thread Rephoto_iOS/Utilities/Keychain/KeychainTokenStore.swift
- NetworkClient: 일시적 오류와 인증 실패를 구분하여 강제 로그아웃 방지
- TokenPair: 일반 struct에서 불필요한 nonisolated 제거
- MoyaNetworkAdapter: target.baseURL 대신 주입된 self.baseURL 사용
- KeychainTokenStore: refreshToken 저장 실패 시 accessToken 롤백
@ddodle ddodle merged commit d8b938d into main May 31, 2026
2 checks passed
@ddodle ddodle deleted the refactor/keychain-actor branch May 31, 2026 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

♻️ Refactor: Token 관리 Keychain + Actor 전환

1 participant