MLSAppFeature를 구체화 합니다.#338
Conversation
There was a problem hiding this comment.
Code Review
This pull request modularizes the project by migrating monolithic code into separate Swift packages and setting up modular dependency injection assemblies. While the refactoring improves the architecture, several critical issues were identified: a circular dependency and redundant target declarations in Package.swift, missing server-side FCM token updates in TokenLauncher, and compiler ambiguity errors due to duplicate UserDefaultsRepository definitions across imported interfaces. Additionally, wrapping the AppCoordinator registration in MainActor.assumeIsolated is recommended to avoid strict concurrency compilation errors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if case .success(let accessToken) = | ||
| tokenRepository.fetchToken(type: .accessToken), | ||
| !accessToken.isEmpty { | ||
| _ = tokenRepository.saveToken( | ||
| type: .fcmToken, | ||
| value: token | ||
| ) | ||
|
|
||
| os_log("Request to update FCM token on server") | ||
| } else { | ||
| os_log("Not logged in yet, skipping FCM update to server") | ||
| } | ||
| } |
There was a problem hiding this comment.
| dependencies: [ | ||
| .package(path: "../MLSAppFeature"), | ||
| .package(path: "../MLSAuthFeature"), | ||
| .package(path: "../MLSBookmarkFeature"), | ||
| .package(path: "../MLSCore"), | ||
| .package(path: "../MLSDictionaryFeature"), | ||
| .package(path: "../MLSMyPageFeature"), | ||
| .package(path: "../MLSRecommendationFeature"), | ||
| .package(url: "https://github.com/ReactiveX/RxSwift.git", from: "6.7.0") | ||
| ], |
There was a problem hiding this comment.
정의 중인 패키지(MLSAppFeature)가 자기 자신(../MLSAppFeature)을 의존성으로 추가하고 있습니다. 이는 순환 참조 및 SPM 로드 에러를 유발합니다. 이를 제거해야 합니다.
dependencies: [
.package(path: "../MLSAuthFeature"),
.package(path: "../MLSBookmarkFeature"),
.package(path: "../MLSCore"),
.package(path: "../MLSDictionaryFeature"),
.package(path: "../MLSMyPageFeature"),
.package(path: "../MLSRecommendationFeature"),
.package(url: "https://github.com/ReactiveX/RxSwift.git", from: "6.7.0"),
],| dependencies: [ | ||
| "MLSAppFeatureInterface", | ||
| .product(name: "MLSAuthFeature", package: "MLSAuthFeature"), | ||
| .product(name: "MLSAppFeatureInterface", package: "MLSAppFeature"), | ||
| .product(name: "MLSAuthFeatureInterface", package: "MLSAuthFeature"), | ||
| .product(name: "MLSBookmarkFeature", package: "MLSBookmarkFeature"), | ||
| .product(name: "MLSBookmarkFeatureInterface", package: "MLSBookmarkFeature"), | ||
| .product(name: "MLSCore", package: "MLSCore"), | ||
| .product(name: "MLSDictionaryFeature", package: "MLSDictionaryFeature"), | ||
| .product(name: "MLSDictionaryFeatureInterface", package: "MLSDictionaryFeature"), | ||
| .product(name: "MLSMyPageFeature", package: "MLSMyPageFeature"), | ||
| .product(name: "MLSMyPageFeatureInterface", package: "MLSMyPageFeature"), | ||
| .product(name: "MLSRecommendationFeature", package: "MLSRecommendationFeature"), | ||
| .product(name: "MLSRecommendationFeatureInterface", package: "MLSRecommendationFeature"), | ||
| .product(name: "RxSwift", package: "RxSwift") | ||
| ] |
There was a problem hiding this comment.
타겟 의존성에서 동일 패키지 내의 타겟인 MLSAppFeatureInterface를 외부 프로덕트 형식(.product(name: "MLSAppFeatureInterface", package: "MLSAppFeature"))으로 중복 추가하고 있습니다. 이를 제거하고 내부 타겟 이름만 지정하도록 수정해야 합니다.
dependencies: [
"MLSAppFeatureInterface",
.product(name: "MLSAuthFeature", package: "MLSAuthFeature"),
.product(name: "MLSAuthFeatureInterface", package: "MLSAuthFeature"),
.product(name: "MLSBookmarkFeature", package: "MLSBookmarkFeature"),
.product(name: "MLSBookmarkFeatureInterface", package: "MLSBookmarkFeature"),
.product(name: "MLSCore", package: "MLSCore"),
.product(name: "MLSDictionaryFeature", package: "MLSDictionaryFeature"),
.product(name: "MLSDictionaryFeatureInterface", package: "MLSDictionaryFeature"),
.product(name: "MLSMyPageFeature", package: "MLSMyPageFeature"),
.product(name: "MLSMyPageFeatureInterface", package: "MLSMyPageFeature"),
.product(name: "MLSRecommendationFeature", package: "MLSRecommendationFeature"),
.product(name: "MLSRecommendationFeatureInterface", package: "MLSRecommendationFeature"),
.product(name: "RxSwift", package: "RxSwift"),
]| userDefaultsRepository: DIContainer.resolve( | ||
| type: UserDefaultsRepository.self, name: "authUserDefaultsRepository" | ||
| ) |
There was a problem hiding this comment.
현재 파일에서 MLSAuthFeatureInterface와 MLSDictionaryFeatureInterface를 모두 임포트하고 있습니다. 두 인터페이스 모듈 모두 UserDefaultsRepository 프로토콜을 정의하고 있기 때문에, 모듈 명시 없이 UserDefaultsRepository.self를 사용하면 컴파일러가 모호성(Ambiguity) 에러를 발생시킵니다. 이를 방지하기 위해 MLSAuthFeatureInterface.UserDefaultsRepository.self와 같이 명시적으로 타입을 지정해 주어야 합니다.
| userDefaultsRepository: DIContainer.resolve( | |
| type: UserDefaultsRepository.self, name: "authUserDefaultsRepository" | |
| ) | |
| userDefaultsRepository: DIContainer.resolve( | |
| type: MLSAuthFeatureInterface.UserDefaultsRepository.self, name: "authUserDefaultsRepository" | |
| ) |
| DIContainer.register(type: FetchVisitDictionaryDetailUseCase.self) { | ||
| FetchVisitDictionaryDetailUseCaseImpl(repository: DIContainer.resolve(type: UserDefaultsRepository.self, name: "dictionaryUserDefaultsRepository")) | ||
| } |
There was a problem hiding this comment.
현재 파일에서 MLSAuthFeatureInterface와 MLSDictionaryFeatureInterface를 모두 임포트하고 있습니다. 두 인터페이스 모듈 모두 UserDefaultsRepository 프로토콜을 정의하고 있기 때문에, 모듈 명시 없이 UserDefaultsRepository.self를 사용하면 컴파일러가 모호성(Ambiguity) 에러를 발생시킵니다. 이를 방지하기 위해 MLSDictionaryFeatureInterface.UserDefaultsRepository.self와 같이 명시적으로 타입을 지정해 주어야 합니다.
| DIContainer.register(type: FetchVisitDictionaryDetailUseCase.self) { | |
| FetchVisitDictionaryDetailUseCaseImpl(repository: DIContainer.resolve(type: UserDefaultsRepository.self, name: "dictionaryUserDefaultsRepository")) | |
| } | |
| DIContainer.register(type: FetchVisitDictionaryDetailUseCase.self) { | |
| FetchVisitDictionaryDetailUseCaseImpl(repository: DIContainer.resolve(type: MLSDictionaryFeatureInterface.UserDefaultsRepository.self, name: "dictionaryUserDefaultsRepository")) | |
| } |
| userDefaultsRepository: DIContainer | ||
| .resolve(type: UserDefaultsRepository.self, name: "authUserDefaultsRepository") | ||
| ) |
There was a problem hiding this comment.
현재 파일에서 MLSAuthFeatureInterface와 MLSDictionaryFeatureInterface를 모두 임포트하고 있습니다. 두 인터페이스 모듈 모두 UserDefaultsRepository 프로토콜을 정의하고 있기 때문에, 모듈 명시 없이 UserDefaultsRepository.self를 사용하면 컴파일러가 모호성(Ambiguity) 에러를 발생시킵니다. 이를 방지하기 위해 MLSAuthFeatureInterface.UserDefaultsRepository.self와 같이 명시적으로 타입을 지정해 주어야 합니다.
| userDefaultsRepository: DIContainer | |
| .resolve(type: UserDefaultsRepository.self, name: "authUserDefaultsRepository") | |
| ) | |
| userDefaultsRepository: DIContainer | |
| .resolve(type: MLSAuthFeatureInterface.UserDefaultsRepository.self, name: "authUserDefaultsRepository") | |
| ) |
| userDefaultsRepository: DIContainer.resolve( | ||
| type: UserDefaultsRepository.self, name: "authUserDefaultsRepository" | ||
| ) |
There was a problem hiding this comment.
현재 파일에서 MLSAuthFeatureInterface와 MLSDictionaryFeatureInterface를 모두 임포트하고 있습니다. 두 인터페이스 모듈 모두 UserDefaultsRepository 프로토콜을 정의하고 있기 때문에, 모듈 명시 없이 UserDefaultsRepository.self를 사용하면 컴파일러가 모호성(Ambiguity) 에러를 발생시킵니다. 이를 방지하기 위해 MLSAuthFeatureInterface.UserDefaultsRepository.self와 같이 명시적으로 타입을 지정해 주어야 합니다.
| userDefaultsRepository: DIContainer.resolve( | |
| type: UserDefaultsRepository.self, name: "authUserDefaultsRepository" | |
| ) | |
| userDefaultsRepository: DIContainer.resolve( | |
| type: MLSAuthFeatureInterface.UserDefaultsRepository.self, name: "authUserDefaultsRepository" | |
| ) |
| DIContainer.register(type: AppCoordinatorProtocol.self) { | ||
| AppCoordinator( | ||
| window: window, | ||
| recommendationMainFactory: DIContainer.resolve( | ||
| type: RecommendationMainFactory.self | ||
| ), | ||
| dictionaryMainViewFactory: DIContainer.resolve( | ||
| type: DictionaryMainViewFactory.self | ||
| ), | ||
| bookmarkMainFactory: DIContainer.resolve( | ||
| type: BookmarkMainFactory.self | ||
| ), | ||
| myPageMainFactory: DIContainer.resolve( | ||
| type: MyPageMainFactory.self | ||
| ), | ||
| loginFactory: DIContainer.resolve(type: LoginFactory.self) | ||
| ) | ||
| } |
There was a problem hiding this comment.
AppCoordinator는 @MainActor로 격리된 클래스이지만, DIContainer.register에 전달되는 클로저는 비격리(Non-isolated) 컨텍스트입니다. Swift 5.10/6의 엄격한 동시성 검사(Strict Concurrency) 환경에서는 비격리 컨텍스트에서 메인 액터 격리 객체를 직접 초기화할 때 컴파일 에러가 발생할 수 있습니다. 이 등록 클로저는 항상 메인 스레드에서 실행되므로, MainActor.assumeIsolated를 사용하여 안전하게 초기화하도록 감싸는 것을 권장합니다.
DIContainer.register(type: AppCoordinatorProtocol.self) {
MainActor.assumeIsolated {
AppCoordinator(
window: window,
recommendationMainFactory: DIContainer.resolve(
type: RecommendationMainFactory.self
),
dictionaryMainViewFactory: DIContainer.resolve(
type: DictionaryMainViewFactory.self
),
bookmarkMainFactory: DIContainer.resolve(
type: BookmarkMainFactory.self
),
myPageMainFactory: DIContainer.resolve(
type: MyPageMainFactory.self
),
loginFactory: DIContainer.resolve(type: LoginFactory.self)
)
}
}
📌 이슈
✅ 작업 사항
👀 ETC (추후 개발해야 할 것, 참고자료 등) ->