Skip to content

Feat/#470 강제 업데이트 구현#471

Open
y-eonee wants to merge 8 commits into
developfrom
feat/#470-강제-업데이트-구현

Hidden character warning

The head ref may contain hidden characters: "feat/#470-\uac15\uc81c-\uc5c5\ub370\uc774\ud2b8-\uad6c\ud604"
Open

Feat/#470 강제 업데이트 구현#471
y-eonee wants to merge 8 commits into
developfrom
feat/#470-강제-업데이트-구현

Conversation

@y-eonee

@y-eonee y-eonee commented Jul 3, 2026

Copy link
Copy Markdown
Member

🔗 연결된 이슈

📄 작업 내용

  • Firebase Remote Config를 이용하여 최소 버전보다 낮을 시 업데이트를 강제하도록 하였습니다.
  • SplashVC에서 앱스토어로 이동하도록 하는 alert이 뜨고 앱스토어로 이동할 수 있도록 구현하였습니다.
  • 모달을 피그마에서 못찾아서 안드 PR보고 쫌쫌따리 맞췄습니당..ㅎㅎ
구현 내용 IPhone 17 pro
GIF

💻 주요 코드 설명

Firebase Remote Config에 min_version_code 매개변수 추가

image

현재 최소버전은 다음 업데이트때 반영될 1.3.0으로 걸어둔 상태입니다!

전체 플로우

  1. SplahVC에 진입하면 강제 업데이트 여부를 확인합니다.
  2. 업데이트가 필요하다면 modal을 띄우고 유저가 앱스토어로 이동하게 합니다.
  3. 업데이트가 필요하지 않다면 autoLogin을 실행합니다.

ForceUpdateManager

  • 버전 비교는 String의 compare 메소드를 이용합니다. 최소 버전보다 낮을 때 true를 리턴합니다.
  func checkForUpdate() async -> Bool {  // usecase에서 이 함수를 호출합니다. 
        do {
            try await remoteConfig.fetch()
            try await remoteConfig.activate()

            let minimumVersion = remoteConfig["min_version_code"].stringValue
            
            return isUpdateRequired(minimumVersion: minimumVersion)
        } catch {
            return false
        }
    }

    private var currentAppVersion: String { // 현재 앱 버전을 가져옵니다. 
        Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
    }

    private func isUpdateRequired(minimumVersion: String) -> Bool {
        ByeBooLogger.debug("현재버전: \(currentAppVersion), 최소버전: \(minimumVersion)")
        return currentAppVersion.compare(minimumVersion, options: .numeric) == .orderedAscending 
    }

의존성 주입 부분에서 고민을 많이 했는데 혹시 더 좋은 방법이 있다면 말씀해주세요 !!!!!

Summary by CodeRabbit

요약

  • New Features
    • 앱 시작 시 원격 설정을 기반으로 강제 업데이트 필요 여부를 확인하고, 필요 시 업데이트 안내 모달을 표시합니다.
    • 모달에서 앱스토어로 바로 이동할 수 있습니다.
  • Bug Fixes
    • 앱 활성화 시 시작 화면 전환 흐름이 더 안정적으로 동작하도록 개선했습니다.
  • Chores
    • 강제 업데이트 체크용 유스케이스/리포지토리 구성과 관련 상수·화면 구성을 정리했습니다.

@y-eonee y-eonee self-assigned this Jul 3, 2026
@y-eonee y-eonee added 나연🐹 feat 새로운 기능 구현 및 API 연결 labels Jul 3, 2026
@y-eonee y-eonee linked an issue Jul 3, 2026 that may be closed by this pull request
1 task
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1fd38510-a20e-4b4b-b575-69010cedf74b

📥 Commits

Reviewing files that changed from the base of the PR and between 716c996 and bfe9b52.

📒 Files selected for processing (2)
  • ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift

Walkthrough

Firebase Remote Config 기반 강제 업데이트 판정 로직이 추가되었고, Splash 화면은 업데이트 필요 여부에 따라 모달 표시 또는 자동 로그인으로 분기하도록 변경되었습니다. 관련 DI 등록, 유스케이스, 모달 UI, 앱스토어 상수도 함께 추가되었습니다.

Changes

강제 업데이트 기능

Layer / File(s) Summary
의존성 및 계약 추가
ByeBoo-iOS.xcodeproj/project.pbxproj, .../ForeceUpdateInterface.swift, .../CheckForceUpdateUseCase.swift, .../DomainDependencyAssembler.swift
FirebaseRemoteConfig 제품 의존성, 강제 업데이트 인터페이스/유스케이스, DI 등록이 추가됨.
Remote Config 판정 구현
.../ForceUpdateManager.swift
Remote Config를 fetch/activate한 뒤 최소 버전과 현재 앱 버전을 비교해 업데이트 필요 여부를 반환하는 구현이 추가됨.
SplashViewModel 상태 분기
.../SplashViewModel.swift, .../PresentationDependencyAssembler.swift
강제 업데이트 확인 결과를 output으로 노출하고, viewDidLoad/tryAutoLogin 입력 분기와 의존성 주입이 갱신됨.
Splash 화면 UI 연동
.../SplashViewController.swift, .../ForceUpdateModalView.swift, .../AppConstants.swift
active 알림 관찰, 강제 업데이트 모달 표시, 앱스토어 열기, 관련 UI/상수가 추가됨.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SplashViewController
  participant SplashViewModel
  participant CheckForceUpdateUseCase
  participant DefaultForceUpdateRepository
  participant RemoteConfig
  participant ForceUpdateModalView

  SplashViewController->>SplashViewModel: action(.viewDidLoad)
  SplashViewModel->>CheckForceUpdateUseCase: execute()
  CheckForceUpdateUseCase->>DefaultForceUpdateRepository: checkForUpdate()
  DefaultForceUpdateRepository->>RemoteConfig: fetch()
  DefaultForceUpdateRepository->>RemoteConfig: activate()
  DefaultForceUpdateRepository-->>CheckForceUpdateUseCase: Bool
  CheckForceUpdateUseCase-->>SplashViewModel: Bool
  SplashViewModel-->>SplashViewController: forceUpdatePublisher(Bool)
  alt 업데이트 필요
    SplashViewController->>ForceUpdateModalView: present
  else 업데이트 불필요
    SplashViewController->>SplashViewModel: action(.tryAutoLogin)
  end
Loading

Suggested reviewers: juri123123, dev-domo

Poem

토끼가 귀를 쫑긋, 버전을 살펴봐요 🐰
리모트 설정에서 숫자를 꺼내고
새 버전 아니면 폴짝 자동 로그인
필요하면 모달 띄워 조용히 안내해요
당근처럼 선명한 흐름, 참 좋네요 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 강제 업데이트 구현과 Firebase Remote Config, Splash 진입 흐름 변경이라는 주요 변경 사항을 잘 요약합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 feat/#470-강제-업데이트-구현

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.

@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: 3

🧹 Nitpick comments (7)
ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift (1)

14-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

타입명 표기 불일치: UsecaseUseCase.

DefaultCheckForceUpdateUsecase만 소문자 case로 되어 있어, 코드베이스 전반의 Default*UseCase 명명 규칙과 어긋납니다.

♻️ 제안 수정
-struct DefaultCheckForceUpdateUsecase: CheckForceUpdateUseCase {
+struct DefaultCheckForceUpdateUseCase: CheckForceUpdateUseCase {
🤖 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 `@ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift` at line
14, The type name in DefaultCheckForceUpdateUsecase is inconsistent with the
project’s Default*UseCase naming convention. Rename the struct to
DefaultCheckForceUpdateUseCase and update any matching references so the
CheckForceUpdateUseCase implementation follows the same capitalization pattern
as the rest of the codebase.
ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift (3)

17-21: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

프로덕션에서 minimumFetchInterval = 0은 스로틀링 위험이 있습니다.

디버그/개발용이 아닌 앱 시작 시마다(매 실행) 무조건 fetch interval을 0으로 설정하고 있습니다. Firebase 공식 문서에서는 이 설정을 개발 전용으로 명시하며, 트래픽이 많은 앱에서는 시간당 서버 측 쿼터에 걸려 스로틀링될 수 있다고 경고합니다. 스로틀링이 발생하면 fetch()가 실패하고 checkForUpdate()는 항상 false를 반환하게 되어, 강제 업데이트가 필요한 사용자도 감지하지 못할 수 있습니다.

DEBUG 빌드에서만 0으로 설정하고, 프로덕션에서는 합리적인 간격(예: 몇 시간 단위)을 사용하는 것을 권장합니다.

♻️ 제안 수정
     init() {
         let settings = RemoteConfigSettings()
-        settings.minimumFetchInterval = 0
+        `#if` DEBUG
+        settings.minimumFetchInterval = 0
+        `#else`
+        settings.minimumFetchInterval = 3600
+        `#endif`
         RemoteConfig.remoteConfig().configSettings = settings
     }

Firebase 문서 확인 결과: "During development, it's recommended to set a relatively low minimum fetch interval... Keep in mind that this setting should be used for development only, not for an app running in production."

🤖 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 `@ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift`
around lines 17 - 21, ForceUpdateManager.init() is setting
RemoteConfigSettings.minimumFetchInterval to 0 unconditionally, which should be
limited to development use only. Update the initialization logic in
ForceUpdateManager so that RemoteConfig.remoteConfig().configSettings uses a
zero interval only for DEBUG builds, and uses a reasonable production interval
otherwise; keep the change localized to the init() setup and preserve the
existing RemoteConfig configuration flow.

1-6: 📐 Maintainability & Code Quality | 🔵 Trivial

파일 헤더 주석이 실제 파일명과 다릅니다.

주석에는 RemoteConfigeManager.swift로 되어 있지만 실제 파일명은 ForceUpdateManager.swift입니다.

🤖 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 `@ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift`
around lines 1 - 6, The file header comment is using the wrong filename, so
update the top-of-file header in ForceUpdateManager.swift to match the actual
file name instead of RemoteConfigeManager.swift. Keep the rest of the header
unchanged and ensure any generated template or metadata in this file reflects
ForceUpdateManager.

23-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

throws 선언이 무의미하고, 오류가 조용히 삼켜집니다.

checkForUpdate()는 내부에서 모든 에러를 catch해 항상 false를 반환하므로 실제로는 절대 throw하지 않습니다. 그런데도 프로토콜과 함수 시그니처는 throws를 유지하고 있어, 호출부(CheckForceUpdateUseCase.execute(), SplashViewModel.checkForceUpdate())에서 불필요한 try/do-catch가 이중으로 존재하게 됩니다. 또한 fetch/activate 실패 시 에러 로그가 전혀 남지 않아 실패 원인 파악이 어렵습니다.

throws를 제거해 계약을 명확히 하거나, 최소한 catch 블록에서 에러를 로깅하는 것을 제안합니다.

♻️ 제안 수정 (에러 로깅 추가)
     func checkForUpdate() async throws -> Bool {
         do {
             try await remoteConfig.fetch()
             try await remoteConfig.activate()
 
             let minimumVersion = remoteConfig["min_version_code"].stringValue
             
             return isUpdateRequired(minimumVersion: minimumVersion)
         } catch {
+            ByeBooLogger.debug("강제 업데이트 확인 실패: \(error)")
             return false
         }
     }
🤖 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 `@ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift`
around lines 23 - 34, `ForceUpdateManager.checkForUpdate()` currently swallows
every error and always returns false, so the `throws` on the method and its
protocol contract are misleading. Either remove `throws` from `checkForUpdate()`
and the related call sites that reference it, or keep the non-throwing behavior
but add error logging inside the `catch` so fetch/activate failures are visible.
Update the usages in `CheckForceUpdateUseCase.execute()` and
`SplashViewModel.checkForceUpdate()` accordingly so they no longer wrap this
call in unnecessary try/do-catch logic.
ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift (1)

258-261: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

다른 리포지토리와 달리 DefaultForceUpdateManager()가 DIContainer를 거치지 않고 직접 인스턴스화됩니다.

같은 파일의 다른 모든 등록은 파일 상단 guard let ... = DIContainer.shared.resolve(type: ...Interface.self)로 먼저 리포지토리를 해석한 뒤 UseCase 생성 시 주입하는 패턴을 따릅니다. 반면 CheckForceUpdateUseCase 등록에서는 DefaultForceUpdateManager()를 인라인으로 직접 생성해 주입합니다. 이는 기존 DI 컨벤션과 다르며, 테스트 시 목(mock) 구현체로 교체하기 어렵게 만듭니다.

ForceUpdateManager도 DIContainer에 등록한 뒤 resolve하여 사용하는 방식으로 통일하는 것을 제안합니다.

♻️ 제안 수정
+        DIContainer.shared.register(type: ForceUpdateManager.self) { _ in
+            return DefaultForceUpdateManager()
+        }
+        
+        guard let forceUpdateManager = DIContainer.shared.resolve(type: ForceUpdateManager.self) else {
+            ByeBooLogger.error(ByeBooError.DIFailedError)
+            return
+        }
+        
         DIContainer.shared.register(type: CheckForceUpdateUseCase.self) { _ in
-            return DefaultCheckForceUpdateUsecase(repository: DefaultForceUpdateManager())
+            return DefaultCheckForceUpdateUseCase(repository: forceUpdateManager)
         }
🤖 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 `@ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift` around lines
258 - 261, The CheckForceUpdateUseCase registration is bypassing the existing DI
pattern by creating DefaultForceUpdateManager() directly instead of resolving it
through DIContainer.shared. Update the DomainDependencyAssembler registration so
ForceUpdateManager is registered and then injected via resolve, matching the
other use case bindings in this file and keeping the dependency swappable for
tests. Use the CheckForceUpdateUseCase and DefaultForceUpdateManager symbols to
locate the affected registration and align it with the surrounding repository
injection style.
ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift (1)

60-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

catch 블록에서 에러 로깅 누락

autoLogin()은 실패 시 ByeBooLogger.error(error)로 에러를 남기는 반면, checkForceUpdate()catch는 에러를 그대로 버리고 false만 전송합니다. Remote Config fetch 실패가 강제 업데이트라는 핵심 안전장치의 동작을 좌우하는 만큼, 실패 원인을 남겨두는 것이 운영/디버깅에 유리합니다.

🪵 제안 수정
             } catch {
+                ByeBooLogger.error(error)
                 forceUpdateSubject.send(false)
             }
🤖 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
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift`
around lines 60 - 74, `SplashViewModel.checkForceUpdate()`의 `catch` 블록에서 에러가
버려지고 있어 실패 원인을 추적할 수 없습니다. `autoLogin()`처럼 `ByeBooLogger.error(error)`를 추가해
Remote Config fetch 실패를 로그로 남기고, 그 뒤 기존처럼 `forceUpdateSubject.send(false)`를
유지하도록 수정하세요. `checkForceUpdateUseCase.execute()`를 감싸는 `Task` 내부의 예외 처리 흐름을 찾아
반영하면 됩니다.
ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ForceUpdateModalView.swift (1)

18-19: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

타이틀/설명 라벨에 줄바꿈·가로 제약 추가 권장

titleLabel, descriptionLabelcenterX 제약만 있고 numberOfLines나 leading/trailing 제약이 없습니다. 기본값(numberOfLines = 1)이라 큰 접근성 폰트 크기나 더 긴 문구로 바뀔 경우 텍스트가 잘리거나 모달 경계를 벗어날 수 있습니다.

✏️ 제안 수정
     private let titleLabel = UILabel()
     private let descriptionLabel = UILabel()
     override func setStyle() {
         backgroundColor = .grayscale900
         layer.cornerRadius = 12
         
+        titleLabel.numberOfLines = 0
+        descriptionLabel.numberOfLines = 0
+
         titleLabel.applyByeBooFont(
         titleLabel.snp.makeConstraints {
             $0.top.equalToSuperview().inset(24.adjustedH)
-            $0.centerX.equalToSuperview()
+            $0.horizontalEdges.equalToSuperview().inset(24.adjustedW)
+            $0.centerX.equalToSuperview()
         }
         descriptionLabel.snp.makeConstraints {
             $0.top.equalTo(titleLabel.snp.bottom).offset(16.adjustedH)
-            $0.centerX.equalToSuperview()
+            $0.horizontalEdges.equalToSuperview().inset(24.adjustedW)
+            $0.centerX.equalToSuperview()
         }

Also applies to: 29-39, 43-50

🤖 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 `@ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ForceUpdateModalView.swift`
around lines 18 - 19, `ForceUpdateModalView`의 `titleLabel`과 `descriptionLabel`은
현재 가로 제약이 부족하고 기본 단일 줄 설정이라 긴 문구나 큰 접근성 폰트에서 잘릴 수 있습니다. `setupUI`/레이블 설정 로직에서 두
라벨의 `numberOfLines`를 여러 줄로 허용하고, `centerX`만 쓰는 대신 leading/trailing 제약을 추가해 모달 폭
안에서 줄바꿈되도록 수정하세요. `titleLabel`과 `descriptionLabel`을 참조하는 레이아웃 구성 전체를 함께 점검해 안전한
여백을 유지하세요.
🤖 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 `@ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift`:
- Around line 14-20: Move the CheckForceUpdateUseCase dependency off the
Data-layer ForceUpdateManager and into a Domain interface so the use case only
depends on Domain abstractions. Update DefaultCheckForceUpdateUsecase to
reference the Domain protocol, add or relocate the ForceUpdateManager protocol
under the Domain/Interface pattern used by the other use cases, and make
DefaultForceUpdateManager conform to that protocol.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift`:
- Around line 105-119: `presentForceUpdateModal()` currently presents
`ForceUpdateModalView` through `ModalBuilder`, but the underlying
`CustomModalController` still dismisses on background taps. Update the modal
presentation path so the force-update flow cannot be closed by tapping outside
the modal, keeping only the update action from `openAppstore()` available; use
the `ModalBuilder`/`CustomModalController` configuration or presentation flags
associated with `ForceUpdateModalView` to disable outside-tap dismiss.
- Around line 32-37: In SplashViewController, update the sink inside
bindCheckForceUpdate() to capture self weakly so the Combine subscription stored
in cancellables does not retain the view controller. Keep bindAutoLogin()
unchanged, and make sure the appDidBecomeActive flow still triggers
viewModel.action(.viewDidLoad) only when the VC is alive without creating a
retain cycle.

---

Nitpick comments:
In `@ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift`:
- Around line 17-21: ForceUpdateManager.init() is setting
RemoteConfigSettings.minimumFetchInterval to 0 unconditionally, which should be
limited to development use only. Update the initialization logic in
ForceUpdateManager so that RemoteConfig.remoteConfig().configSettings uses a
zero interval only for DEBUG builds, and uses a reasonable production interval
otherwise; keep the change localized to the init() setup and preserve the
existing RemoteConfig configuration flow.
- Around line 1-6: The file header comment is using the wrong filename, so
update the top-of-file header in ForceUpdateManager.swift to match the actual
file name instead of RemoteConfigeManager.swift. Keep the rest of the header
unchanged and ensure any generated template or metadata in this file reflects
ForceUpdateManager.
- Around line 23-34: `ForceUpdateManager.checkForUpdate()` currently swallows
every error and always returns false, so the `throws` on the method and its
protocol contract are misleading. Either remove `throws` from `checkForUpdate()`
and the related call sites that reference it, or keep the non-throwing behavior
but add error logging inside the `catch` so fetch/activate failures are visible.
Update the usages in `CheckForceUpdateUseCase.execute()` and
`SplashViewModel.checkForceUpdate()` accordingly so they no longer wrap this
call in unnecessary try/do-catch logic.

In `@ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift`:
- Around line 258-261: The CheckForceUpdateUseCase registration is bypassing the
existing DI pattern by creating DefaultForceUpdateManager() directly instead of
resolving it through DIContainer.shared. Update the DomainDependencyAssembler
registration so ForceUpdateManager is registered and then injected via resolve,
matching the other use case bindings in this file and keeping the dependency
swappable for tests. Use the CheckForceUpdateUseCase and
DefaultForceUpdateManager symbols to locate the affected registration and align
it with the surrounding repository injection style.

In `@ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift`:
- Line 14: The type name in DefaultCheckForceUpdateUsecase is inconsistent with
the project’s Default*UseCase naming convention. Rename the struct to
DefaultCheckForceUpdateUseCase and update any matching references so the
CheckForceUpdateUseCase implementation follows the same capitalization pattern
as the rest of the codebase.

In `@ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ForceUpdateModalView.swift`:
- Around line 18-19: `ForceUpdateModalView`의 `titleLabel`과 `descriptionLabel`은
현재 가로 제약이 부족하고 기본 단일 줄 설정이라 긴 문구나 큰 접근성 폰트에서 잘릴 수 있습니다. `setupUI`/레이블 설정 로직에서 두
라벨의 `numberOfLines`를 여러 줄로 허용하고, `centerX`만 쓰는 대신 leading/trailing 제약을 추가해 모달 폭
안에서 줄바꿈되도록 수정하세요. `titleLabel`과 `descriptionLabel`을 참조하는 레이아웃 구성 전체를 함께 점검해 안전한
여백을 유지하세요.

In
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift`:
- Around line 60-74: `SplashViewModel.checkForceUpdate()`의 `catch` 블록에서 에러가 버려지고
있어 실패 원인을 추적할 수 없습니다. `autoLogin()`처럼 `ByeBooLogger.error(error)`를 추가해 Remote
Config fetch 실패를 로그로 남기고, 그 뒤 기존처럼 `forceUpdateSubject.send(false)`를 유지하도록
수정하세요. `checkForceUpdateUseCase.execute()`를 감싸는 `Task` 내부의 예외 처리 흐름을 찾아 반영하면
됩니다.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f8728c5f-3963-49ca-925b-9e62506a522e

📥 Commits

Reviewing files that changed from the base of the PR and between 4a32c61 and ab077e2.

📒 Files selected for processing (9)
  • ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj
  • ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ForceUpdateModalView.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/AppConstants.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift

Comment thread ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift
Comment on lines +105 to +119

private func presentForceUpdateModal() {
let modal = ModalBuilder(
modalView: ForceUpdateModalView(),
action: openAppstore,
rootViewController: self
)
modal.present()
}

private func openAppstore() {
guard let url = URL(string: AppConstants.appStoreURL) else { return }
UIApplication.shared.open(url)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# ModalBuilder/ModalProtocol의 배경 탭/스와이프 dismiss 처리 여부 확인
rg -n -B3 -A20 'class ModalBuilder' ByeBoo-iOS/ByeBoo-iOS --type=swift
rg -n 'dismissButton|modalType|tapGesture|backgroundView' ByeBoo-iOS/ByeBoo-iOS --type=swift -g '*Modal*'

Repository: 36-APPJAM-HEARTZ/BYEBOO-iOS

Length of output: 4217


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== ModalProtocol ==\n'
sed -n '1,120p' ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/ModalProtocol.swift

printf '\n== CustomModalController ==\n'
sed -n '1,180p' ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/CustomModalController.swift

printf '\n== ForceUpdateModalView ==\n'
sed -n '1,120p' ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ForceUpdateModalView.swift

printf '\n== ModalBuilder ==\n'
sed -n '1,160p' ByeBoo-iOS/ByeBoo-iOS/Presentation/Common/Modal/ModalBuilder.swift

Repository: 36-APPJAM-HEARTZ/BYEBOO-iOS

Length of output: 5627


배경 탭으로 닫히지 않게 막아야 합니다
ForceUpdateModalView는 닫기 버튼이 없더라도, CustomModalController가 모달 바깥 탭에서 dismiss(animated: false)를 호출합니다. 강제 업데이트는 업데이트 버튼 외에는 종료되지 않도록 배경 탭 dismiss를 비활성화해야 합니다.

🤖 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
`@ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift`
around lines 105 - 119, `presentForceUpdateModal()` currently presents
`ForceUpdateModalView` through `ModalBuilder`, but the underlying
`CustomModalController` still dismisses on background taps. Update the modal
presentation path so the force-update flow cannot be closed by tapping outside
the modal, keeping only the update action from `openAppstore()` available; use
the `ModalBuilder`/`CustomModalController` configuration or presentation flags
associated with `ForceUpdateModalView` to disable outside-tap dismiss.

@dev-domo dev-domo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

좋네요 감사합니다! 코멘트 확인 부탁드릴게요🙏

private func bindCheckForceUpdate() {
viewModel.output.forceUpdatePublisher
.receive(on: DispatchQueue.main)
.sink { result in

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[weak self] 사용해주면 좋을 것 같아요!

private func checkForceUpdate() {
Task {
do {
if try await checkForceUpdateUseCase.execute() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

execute 메서드가 에러를 던지는 함수가 아니라서 try await -> await으로 호출해도 될 것 같은데 맞을까요?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

오 맞아요 반영하겠습니다 !!


struct DefaultCheckForceUpdateUsecase: CheckForceUpdateUseCase {

private let repository: ForceUpdateManager

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

도메인 영역에 별도의 Interface를 만드는 것이 아니라 데이터 영역에 선언한 프로토콜을 주입하게 된 이유가 있나요?

현재 구현에서는 도메인 영역에서 데이터 영역에 선언된 ForceUpdateManager를 알고 있는 구조가 되는 것 같아서요!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

아 맞네여 😇😇😇 프로토콜을 도메인 레이어로 옮겼습니다 !!

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift (2)

10-17: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

DefaultForceUpdateManager 참조를 DefaultForceUpdateRepository로 맞춰야 합니다. Domain/DomainDependencyAssembler.swift:260에서 존재하지 않는 DefaultForceUpdateManager()를 생성하고 있어 컴파일이 깨집니다. 실제 정의된 타입명에 맞추거나, DI 등록과 클래스명을 함께 정리해 주세요.

🤖 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 `@ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift`
around lines 10 - 17, The DI registration is referencing a non-existent
DefaultForceUpdateManager while the actual type defined here is
DefaultForceUpdateRepository. Update DomainDependencyAssembler to instantiate
the real class name, or rename the repository and its registration consistently
so the symbol names match across the assembler and ForceUpdateManager.swift.

13-30: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

minimumFetchInterval = 0은 프로덕션에서 쓰지 마세요.

지금처럼 항상 0으로 두면 Remote Config fetch throttling이 걸릴 수 있고, checkForUpdate()가 모든 예외를 false로 삼켜서 강제 업데이트가 조용히 우회됩니다.
DEBUG/RELEASE로 분기하거나 프로덕션에서는 기본값 수준의 간격을 쓰고, catch에서는 최소한 에러를 남겨 원인을 추적할 수 있게 해주세요.

🤖 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 `@ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift`
around lines 13 - 30, The ForceUpdateManager init() currently hardcodes
RemoteConfigSettings.minimumFetchInterval to 0, which should be limited to DEBUG
only; change it so production uses a normal fetch interval and only debug builds
use zero. In checkForUpdate(), avoid silently swallowing all errors by logging
the failure in the catch block before returning false, so RemoteConfig
throttling or fetch/activate issues can be traced.
ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift (1)

16-19: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

ForceUpdateManagerForceUpdateInterface로 바꿔야 합니다.
현재 프로젝트에 ForceUpdateManager 타입은 없어서, 이 생성자와 주입 코드가 컴파일되지 않습니다.

🤖 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 `@ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift` around
lines 16 - 19, The CheckForceUpdateUseCase initializer is typed against a
non-existent ForceUpdateManager, so update the dependency to use
ForceUpdateInterface instead. Adjust the repository property and
init(repository:) signature in CheckForceUpdateUseCase to accept the interface,
and make sure any matching dependency injection or call sites pass a
ForceUpdateInterface-conforming instance.
🧹 Nitpick comments (1)
ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift (1)

36-39: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

세그먼트 수가 다른 버전 문자열은 직접 비교하지 마세요
String.compare(_:options: .numeric)는 숫자 구간만 자연 정렬할 뿐, 1.31.3.0처럼 세그먼트 개수가 다른 버전을 같은 값으로 보지 않습니다. CFBundleShortVersionStringmin_version_code 형식이 조금만 달라져도 불필요한 업데이트가 트리거될 수 있으니, 세그먼트를 정수로 파싱해 길이를 맞춘 뒤 비교하는 쪽이 안전합니다.

🤖 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 `@ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift`
around lines 36 - 39, The version check in isUpdateRequired(minimumVersion:) is
relying on String.compare(_:options: .numeric), which can mis-handle version
strings with different segment counts like 1.3 and 1.3.0. Update the comparison
logic in ForceUpdateManager to parse currentAppVersion and minimumVersion into
numeric version segments, normalize missing segments by padding as needed, and
compare the resulting components directly instead of using string comparison.
Keep the existing logging in isUpdateRequired(minimumVersion:) but ensure the
decision is based on the parsed version arrays.
🤖 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.

Outside diff comments:
In `@ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift`:
- Around line 10-17: The DI registration is referencing a non-existent
DefaultForceUpdateManager while the actual type defined here is
DefaultForceUpdateRepository. Update DomainDependencyAssembler to instantiate
the real class name, or rename the repository and its registration consistently
so the symbol names match across the assembler and ForceUpdateManager.swift.
- Around line 13-30: The ForceUpdateManager init() currently hardcodes
RemoteConfigSettings.minimumFetchInterval to 0, which should be limited to DEBUG
only; change it so production uses a normal fetch interval and only debug builds
use zero. In checkForUpdate(), avoid silently swallowing all errors by logging
the failure in the catch block before returning false, so RemoteConfig
throttling or fetch/activate issues can be traced.

In `@ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift`:
- Around line 16-19: The CheckForceUpdateUseCase initializer is typed against a
non-existent ForceUpdateManager, so update the dependency to use
ForceUpdateInterface instead. Adjust the repository property and
init(repository:) signature in CheckForceUpdateUseCase to accept the interface,
and make sure any matching dependency injection or call sites pass a
ForceUpdateInterface-conforming instance.

---

Nitpick comments:
In `@ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift`:
- Around line 36-39: The version check in isUpdateRequired(minimumVersion:) is
relying on String.compare(_:options: .numeric), which can mis-handle version
strings with different segment counts like 1.3 and 1.3.0. Update the comparison
logic in ForceUpdateManager to parse currentAppVersion and minimumVersion into
numeric version segments, normalize missing segments by padding as needed, and
compare the resulting components directly instead of using string comparison.
Keep the existing logging in isUpdateRequired(minimumVersion:) but ensure the
decision is based on the parsed version arrays.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 844f9e58-820f-46ea-8102-d6452d112175

📥 Commits

Reviewing files that changed from the base of the PR and between ab077e2 and 716c996.

📒 Files selected for processing (5)
  • ByeBoo-iOS/ByeBoo-iOS/Data/Persistence/Service/ForceUpdateManager.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/ForeceUpdateInterface.swift
  • ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/CheckForceUpdateUseCase.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift
✅ Files skipped from review due to trivial changes (1)
  • ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/ForeceUpdateInterface.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewController/SplashViewController.swift
  • ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Login/ViewModel/SplashViewModel.swift

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 새로운 기능 구현 및 API 연결 나연🐹

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 강제 업데이트 구현

2 participants