-
Notifications
You must be signed in to change notification settings - Fork 2
Fix/#330 NetworkService, CoreDataService Swift Concurrency 형태로 전환 #332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
5b43749
186cc8c
1cee3cd
0fee4a0
5fd12fa
b6d111b
58133f9
2595f73
2e7c060
498bf82
97bc5e2
4507880
a95a161
3401116
13d5fb4
60364fc
41590cf
b724255
c806a5e
85f0e0f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| // | ||
| // SplashCoordinator.swift | ||
| // App | ||
| // | ||
| // Created by gnksbm on 6/30/25. | ||
| // Copyright © 2025 Pepsi-Club. All rights reserved. | ||
| // | ||
|
|
||
| import UIKit | ||
|
|
||
| import MainFeature | ||
| import FeatureDependency | ||
|
|
||
| protocol SplashCoordinator: Coordinator { | ||
| func startTabFlow() | ||
| func openURL(_ url: URL) | ||
| } | ||
|
|
||
| final class SplashCoordinatorImpl: SplashCoordinator { | ||
| var parent: Coordinator? | ||
| var childs: [Coordinator] = [] | ||
| var navigationController: UINavigationController | ||
| public var coordinatorType: CoordinatorType = .splash | ||
| private let coordinatorProvider: CoordinatorProvider | ||
| private let viewModelDependency: SplashViewModelDependency | ||
|
|
||
| init( | ||
| parent: Coordinator, | ||
| navigationController: UINavigationController, | ||
| coordinatorProvider: CoordinatorProvider, | ||
| viewModelDependency: SplashViewModelDependency | ||
| ) { | ||
| self.parent = parent | ||
| self.navigationController = navigationController | ||
| self.coordinatorProvider = coordinatorProvider | ||
| self.viewModelDependency = viewModelDependency | ||
| } | ||
|
|
||
| func start() { | ||
| let splashViewController = SplashViewController( | ||
| viewModel: SplashViewModel(coordinator: self, dependency: viewModelDependency) | ||
| ) | ||
| navigationController.setViewControllers([splashViewController], animated: false) | ||
| } | ||
|
|
||
| func startTabFlow() { | ||
| let tabBarCoordinator = TabBarCoordinator( | ||
| navigationController: navigationController, | ||
| coordinatorProvider: coordinatorProvider | ||
| ) | ||
| childs.append(tabBarCoordinator) | ||
| tabBarCoordinator.start() | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // | ||
| // SplashViewController.swift | ||
| // App | ||
| // | ||
| // Created by gnksbm on 6/30/25. | ||
| // Copyright © 2025 Pepsi-Club. All rights reserved. | ||
| // | ||
|
|
||
| import UIKit | ||
| import DesignSystem | ||
|
|
||
| import RxSwift | ||
| import RxCocoa | ||
|
|
||
| final class SplashViewController: UIViewController { | ||
| private let viewModel: SplashViewModel | ||
|
|
||
| private let disposeBag: DisposeBag = .init() | ||
|
|
||
| init(viewModel: SplashViewModel) { | ||
| self.viewModel = viewModel | ||
| super.init(nibName: nil, bundle: nil) | ||
| } | ||
|
|
||
| required init?(coder: NSCoder) { | ||
| fatalError("init(coder:) has not been implemented") | ||
| } | ||
|
|
||
| override func viewDidLoad() { | ||
| super.viewDidLoad() | ||
|
|
||
| view.backgroundColor = DesignSystemAsset.changeBlue.color | ||
|
|
||
| let output = viewModel.transform( | ||
| input: .init( | ||
| viewDidLoad: .just(()) | ||
| ) | ||
| ) | ||
|
|
||
| output.alert | ||
| .observe(on: MainScheduler.instance) | ||
| .bind(with: self) { owner, alert in | ||
| let alertController = UIAlertController( | ||
| title: alert.title, | ||
| message: alert.message, | ||
| preferredStyle: .alert | ||
| ) | ||
| alert.actions.forEach { alertAction in | ||
| let alertAction = UIAlertAction( | ||
| title: alertAction.title, | ||
| style: .default | ||
| ) { _ in | ||
| alertAction.handler() | ||
| } | ||
| alertController.addAction(alertAction) | ||
| } | ||
| owner.present(alertController, animated: true) | ||
| } | ||
| .disposed(by: disposeBag) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,123 @@ | ||||||||||||||||||||||||
| // | ||||||||||||||||||||||||
| // SplashViewModel.swift | ||||||||||||||||||||||||
| // App | ||||||||||||||||||||||||
| // | ||||||||||||||||||||||||
| // Created by gnksbm on 6/30/25. | ||||||||||||||||||||||||
| // Copyright © 2025 Pepsi-Club. All rights reserved. | ||||||||||||||||||||||||
| // | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import FeatureDependency | ||||||||||||||||||||||||
| import NetworkService | ||||||||||||||||||||||||
| import CoreDataService | ||||||||||||||||||||||||
| import Data | ||||||||||||||||||||||||
| import Domain | ||||||||||||||||||||||||
| import Core | ||||||||||||||||||||||||
| import FirebaseModule | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import RxSwift | ||||||||||||||||||||||||
| import RxRelay | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| protocol SplashViewModelDependency { | ||||||||||||||||||||||||
| var appVersion: AppVersionInfoResponse { get } | ||||||||||||||||||||||||
| var appStoreID: String { get } | ||||||||||||||||||||||||
| var domainURL: String { get } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| final class SplashViewModel: ViewModel { | ||||||||||||||||||||||||
| private weak var coordinator: SplashCoordinator? | ||||||||||||||||||||||||
| @Injected private var versionCheckUseCase: AppVersionCheckUseCase | ||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 왜 SplashViewModel에서 @injected 를 사용하는게 안 좋은 형태라고 생각하셨는지 궁금합니다. |
||||||||||||||||||||||||
| @Injected private var firebaseLogger: FirebaseLogger | ||||||||||||||||||||||||
| private let dependency: SplashViewModelDependency | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| init( | ||||||||||||||||||||||||
| coordinator: SplashCoordinator, | ||||||||||||||||||||||||
| dependency: SplashViewModelDependency | ||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||
| self.coordinator = coordinator | ||||||||||||||||||||||||
| self.dependency = dependency | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| func transform(input: Input) -> Output { | ||||||||||||||||||||||||
| let alertRelay = PublishRelay<Alert>() | ||||||||||||||||||||||||
| Task { | ||||||||||||||||||||||||
| try await input.viewDidLoad.value | ||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. transform 함수 자체가 viewDidLoad될 때 호출하게 되는데, input으로 시퀀스를 받아서 한번 더 호출하는 이유가 있을까요? |
||||||||||||||||||||||||
| await registerDependency() | ||||||||||||||||||||||||
| do { | ||||||||||||||||||||||||
| let forceUpdate = try await versionCheckUseCase.checkForceUpdateNeeded() | ||||||||||||||||||||||||
| switch forceUpdate { | ||||||||||||||||||||||||
| case .notNeeded: | ||||||||||||||||||||||||
| await MainActor.run { | ||||||||||||||||||||||||
| coordinator?.startTabFlow() | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| case .needed(let appStoreURL): | ||||||||||||||||||||||||
| let alert = Alert( | ||||||||||||||||||||||||
| title: "업데이트 알림", | ||||||||||||||||||||||||
| message: "더 나은 서비스를 위해 업데이트 되었어요 ! 업데이트 해주세요." | ||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||
| AlertAction(title: "업데이트") { [weak self] in | ||||||||||||||||||||||||
| self?.coordinator?.openURL(appStoreURL) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| alertRelay.accept(alert) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||
| firebaseLogger.log(name: "강제 업데이트 실패: \(error.localizedDescription)") | ||||||||||||||||||||||||
| await MainActor.run { | ||||||||||||||||||||||||
| coordinator?.startTabFlow() | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| return .init(alert: alertRelay.asObservable()) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private func registerDependency() async { | ||||||||||||||||||||||||
| let coreDataContainer = await CoreDataContainerBuilder().buildContainer() | ||||||||||||||||||||||||
|
Comment on lines
+73
to
+74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Core Data 초기화 에러 처리 필요
private func registerDependency() async {
- let coreDataContainer = await CoreDataContainerBuilder().buildContainer()
+ do {
+ let coreDataContainer = try await CoreDataContainerBuilder().buildContainer()
+ // 나머지 의존성 등록...
+ } catch {
+ DIContainer.firebaseLogger?.log("Core Data 초기화 실패: \(error)")
+ // 치명적 에러이므로 사용자에게 알림 필요
+ throw error
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| let firebaseLogger = FirebaseLoggerImpl() | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| DIContainer.setLogger(firebaseLogger) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| DIContainer.register(type: CoreDataStorage.self, CoreDataStorageImpl(container: coreDataContainer)) | ||||||||||||||||||||||||
| DIContainer.register(type: CoreDataService.self, DefaultCoreDataService()) | ||||||||||||||||||||||||
| DIContainer.register(type: NetworkService.self, DefaultNetworkService()) | ||||||||||||||||||||||||
| DIContainer.register(type: LocationService.self, DefaultLocationService()) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| DIContainer.register(type: AsyncFavoritesRepository.self, AsyncFavoritesRepositoryImpl()) | ||||||||||||||||||||||||
| DIContainer.register(type: FavoritesRepository.self, DefaultFavoritesRepository()) | ||||||||||||||||||||||||
| DIContainer.register(type: BusStopArrivalInfoRepository.self, DefaultBusStopArrivalInfoRepository()) | ||||||||||||||||||||||||
| DIContainer.register(type: StationListRepository.self, DefaultStationListRepository()) | ||||||||||||||||||||||||
| DIContainer.register(type: RegularAlarmRepository.self, DefaultRegularAlarmRepository()) | ||||||||||||||||||||||||
| DIContainer.register(type: LocalNotificationService.self, DefaultLocalNotificationService()) | ||||||||||||||||||||||||
| DIContainer.register(type: RegularAlarmEditingService.self, DefaultRegularAlarmEditingService()) | ||||||||||||||||||||||||
| DIContainer.register( | ||||||||||||||||||||||||
| type: VersionCheckRepository.self, | ||||||||||||||||||||||||
| VersionCheckRepositoryImpl( | ||||||||||||||||||||||||
| appStoreID: dependency.appStoreID, | ||||||||||||||||||||||||
| domainURL: dependency.domainURL | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| DIContainer.register(type: FavoritesUseCase.self, DefaultFavoritesUseCase()) | ||||||||||||||||||||||||
| DIContainer.register(type: RegularAlarmUseCase.self, DefaultRegularAlarmUseCase()) | ||||||||||||||||||||||||
| DIContainer.register(type: AddRegularAlarmUseCase.self, DefaultAddRegularAlarmUseCase()) | ||||||||||||||||||||||||
| DIContainer.register(type: SearchUseCase.self, DefaultSearchUseCase()) | ||||||||||||||||||||||||
| DIContainer.register(type: BusStopUseCase.self, DefaultBusStopUseCase()) | ||||||||||||||||||||||||
| DIContainer.register(type: NearMapUseCase.self, DefaultNearMapUseCase()) | ||||||||||||||||||||||||
| DIContainer.register(type: FirebaseLogger.self, firebaseLogger) | ||||||||||||||||||||||||
| DIContainer.register( | ||||||||||||||||||||||||
| type: AppVersionCheckUseCase.self, | ||||||||||||||||||||||||
| VersionCheckUseCaseImpl( | ||||||||||||||||||||||||
| currentVersion: dependency.appVersion | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| extension SplashViewModel { | ||||||||||||||||||||||||
| struct Input { | ||||||||||||||||||||||||
| let viewDidLoad: Single<Void> | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| struct Output { | ||||||||||||||||||||||||
| let alert: Observable<Alert> | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
구조체 이름 앞에 Default를 안쓰고 뒤에 Impl를 붙이는 걸로 컨벤션이 바뀌는 걸까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
구현체라는 표현을 목적으로 네이밍을 수정해봤는데 어떤지 의견 부탁드려요! @isakatty