Skip to content

feat: GA 태그 추가#211

Merged
clxxrlove merged 21 commits intodevelopfrom
BOOK-267-feature/#174
Sep 15, 2025
Merged

feat: GA 태그 추가#211
clxxrlove merged 21 commits intodevelopfrom
BOOK-267-feature/#174

Conversation

@doyeonk429
Copy link
Member

@doyeonk429 doyeonk429 commented Sep 12, 2025

🔗 관련 이슈

📘 작업 유형

  • ✨ Feature (기능 추가)
  • 🐞 Bugfix (버그 수정)
  • 🔧 Refactor (코드 리팩토링)
  • ⚙️ Chore (환경 설정)
  • 📝 Docs (문서 작성 및 수정)
  • ✅ Test (기능 테스트)
  • 🎨 style (코드 스타일 수정)

📙 작업 내역

  • 데이터 수집을 위한 Google Analytics을 추가하였습니다.

🧪 테스트 내역

  • 브라우저/기기에서 동작 확인
  • 엣지 케이스 테스트 완료
  • 기존 기능 영향 없음

✅ PR 체크리스트

  • 커밋 메시지가 명확합니다
  • PR 제목이 컨벤션에 맞습니다
  • 관련 이슈 번호를 작성했습니다
  • 기능이 정상적으로 작동합니다
  • 불필요한 코드를 제거했습니다

💬 추가 설명 or 리뷰 포인트 (선택)

  • GA4를 시뮬레이터에서 바로 구동할 경우, 태그 수집이 되지 않습니다.

Summary by CodeRabbit

  • New Features

    • 중앙화된 GA 추적 네임스페이스(GA 키) 및 분석 초기화 매니저(GAManager) 추가.
    • ScreenLoggable 프로토콜 도입으로 타입 기반 화면 로깅 지원.
  • Enhancements

    • 여러 화면에 viewDidAppear 기반 자동 화면 로그 추가(온보딩, 홈, 검색, 노트, 북 상세 등).
    • 저장·공유·삭제·페이지 전환 등 주요 사용자 액션 이벤트 로깅 추가.
    • 노트 완료 화면에 우측 상단 ‘더 보기’ 버튼(공유·편집·삭제) 추가.
    • 페이지 컨트롤 currentPage에 KVO 호환성(@objc dynamic) 추가.
    • 검색 상태에 viewType 저장으로 흐름 추적 개선.
  • Chores

    • Firebase Analytics 의존성 추가, 초기화 호출 및 Info.plist 자동 화면 보고 비활성화, 디버그 런치 인자 추가.

@doyeonk429 doyeonk429 self-assigned this Sep 12, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 12, 2025

Walkthrough

Firebase Analytics 통합: GAManager와 GATracking 네임스페이스를 추가하고 ScreenLoggable 프로토콜로 다수의 뷰컨트롤러·코디네이터에 화면/이벤트 로깅을 삽입했으며, 의존성·스킴·Info.plist 설정도 Analytics용으로 갱신했다.

Changes

Cohort / File(s) Change Summary
Analytics 초기화·관리
src/Projects/BKCore/Sources/Tracking/GAManager.swift
GAManager 추가. configureAnalytics()로 수집 제어, consent 설정 및 디바이스/로케일/타임존 등 기본 이벤트 파라미터 설정.
GA 이벤트 키 네임스페이스
src/Projects/BKCore/Sources/Tracking/GATracking.swift
새 공개 GATracking enum 도입 — 온보딩·홈·검색·기록·카드·설정·오류별 정적 문자열 상수 추가.
스크린 로깅 프로토콜
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift
ScreenLoggable 프로토콜과 UIViewController 전용 logScreenView(name:), 일반 logGoogleAnalytics(name:) 기본 구현 추가(파라미터: screen_name, screen_class).
Core 의존성·패키지 변경
src/Projects/BKCore/Project.swift, src/Tuist/Package.swift
BKCore 타깃에 .external(.FirebaseAnalytics) 추가; Tuist 패키지의 FirebaseAnalyticsSwift 제품타입 매핑 삭제.
앱 초기화·Info.plist·스킴
src/Projects/Booket/Sources/AppDelegate.swift, src/Projects/Booket/Project.swift, src/SupportingFiles/Booket/Info.plist
AppDelegate에서 GAManager.configureAnalytics() 호출 추가. Reed‑Debug 타깃에 FirebaseAnalytics 추가 및 런치인자 -FIRAnalyticsDebugEnabled 설정. Info.plist에 FirebaseAutomaticScreenReportingEnabled = false 추가.
ScreenLoggable 채택 (코디네이터·뷰컨트롤러)
src/Projects/BKPresentation/.../AppCoordinator.swift, .../AuthFlow/.../LoginViewController.swift, .../AuthFlow/.../TermsViewController.swift, .../OnboardingFlow/.../OnboardingViewController.swift, .../MainFlow/Home/View/HomeViewController.swift, .../ArchiveFlow/View/ArchiveViewController.swift, .../MainFlow/Search/View/SearchViewController.swift, .../MainFlow/Setting/View/SettingViewController.swift, .../MainFlow/Note/.../*.swift, .../MainFlow/BookDetail/.../*.swift, .../MainFlow/NoteCompletion/.../NoteCompletionViewController.swift
다수의 코디네이터·뷰컨트롤러에 ScreenLoggable 채택 및 screenName 추가, viewDidAppear에서 logScreenView() 호출 삽입. 특정 상호작용(로그인 오류, 삭제/삭제완료, 저장, 공유, 페이지 전환, 검색/등록 흐름 등)에 이벤트 로깅 추가.
BookDetail ViewModel 변경
src/Projects/BKPresentation/Sources/MainFlow/BookDetail/ViewModel/BookDetailViewModel.swift
상태에 deleteRecordCompleted: Bool 추가, initDeleteRecordValue 액션 추가 및 삭제 성공 시 플래그 설정/초기화 처리 추가.
디자인 컴포넌트 KVO 준비
src/Projects/BKDesign/Sources/Components/BKPageControl.swift
currentPage@objc dynamic 어노테이션 추가(KVO/Obj‑C 호환).
사소한 정리/형식 변경
src/Projects/BKPresentation/Sources/MainFlow/Setting/ViewModel/SettingViewModel.swift
불필요한 빈 줄 제거(기능상 변화 없음).

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant App as AppDelegate
  participant FirebaseCore as FirebaseCore
  participant GA as GAManager
  participant FA as FirebaseAnalytics

  User->>App: 앱 실행
  App->>FirebaseCore: FirebaseApp.configure()
  App->>GA: GAManager.configureAnalytics()
  GA->>FA: setAnalyticsCollectionEnabled(...)
  GA->>FA: setConsent(adStorage/adUserData/adPersonalization)
  GA->>FA: setDefaultEventParameters(device/locale/time_zone/...)
Loading
sequenceDiagram
  participant VC as UIViewController (ScreenLoggable)
  participant Ext as ScreenLoggable Extension
  participant FA as FirebaseAnalytics

  VC->>VC: viewDidAppear(_:)
  VC->>Ext: logScreenView(name?)
  Ext->>FA: logEvent(name: chosenName, parameters: {screen_name, screen_class})
Loading
sequenceDiagram
  participant Detail as BookDetailViewController
  participant VM as BookDetailViewModel
  participant FA as FirebaseAnalytics

  Detail->>FA: logEvent(deleteBook) or logEvent(delete)
  alt 도서 삭제 완료
    Detail->>FA: logEvent(deleteBookComplete)
    Detail->>Detail: popViewController()
  else 기록 삭제 완료
    VM-->>Detail: deleteRecordSuccessed
    Detail->>FA: logEvent(deleteComplete)
    Detail->>VM: initDeleteRecordValue
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning 대부분 변경은 GA 계측 관련이지만 몇몇 변경은 범위를 벗어날 가능성이 있어 추가 확인이 필요합니다: NoteCompletionViewController에 추가된 신규 초기화 경로·coordinator·UI 동작(탭바 숨김, 커스텀 백버튼, 액션 시트 등)은 단순 계측을 넘는 기능 변화로 보입니다; SearchViewController의 네비게이션 타이틀 변경(“도서 검색”→"")과 Tuist/Package.swift의 productTypes 삭제 및 Projects/Booket/Project.swift의 런(디버그) 구성 변경도 빌드·런타임 동작에 영향을 줄 수 있으므로 의도 확인이 필요합니다. 해결 제안: UI·네비게이션·초기화 경로 변경(예: NoteCompletionViewController)은 별도 PR로 분리하거나 PR 설명에 변경 이유와 테스트 결과를 명확히 기재하고, Tuist/Project.swift 관련 변경은 CI와 로컬 빌드 테스트를 통과했는지 확인한 후 병합하십시오.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed PR 제목 "feat: GA 태그 추가"는 변경의 핵심(애플리케이션 전반의 GA/Firebase Analytics 통합 및 태깅 추가)을 간결하게 요약하고 있어 한눈에 주요 변경을 파악하기에 적절합니다.
Linked Issues Check ✅ Passed PR은 연관 이슈 [#174]의 주요 코딩 목표인 라이브러리 임포트와 이벤트 로깅을 충족합니다; GAManager·GATracking·ScreenLoggable 추가 및 여러 뷰컨트롤러의 log 호출은 "add to log event" 요구사항을 구현합니다. Info.plist 관련 ATT 설정과 사용자 식별(identify user)은 이슈 및 PR 설명에서 취소/미적용으로 표기되어 있으며 해당 구현은 포함되어 있지 않습니다. 전체적으로 코드 수준의 GA 설정과 이벤트 계측은 링크된 이슈의 의도와 일치합니다.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BOOK-267-feature/#174

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (1)
src/Projects/BKPresentation/Sources/MainFlow/Search/View/SearchViewController.swift (1)

151-158: 결과 스냅샷 적용 시 ‘result’ 이벤트가 최근 검색 화면에서도 발생 가능

현재는 searchState가 recent일 때도 result 이벤트가 로깅될 수 있어 데이터 왜곡 우려가 있습니다. result 상태일 때만 로깅하도록 가드해 주세요.

-            .sink { [weak self] snapshot in
-                self?.logScreenView(name: GATracking.SearchAndRegister.result)
-                self?.contentView.applySnapshot(
+            .sink { [weak self] snapshot in
+                if case .result = snapshot.state {
+                    self?.logScreenView(name: GATracking.SearchAndRegister.result)
+                }
+                self?.contentView.applySnapshot(
                     with: snapshot.state,
                     count: snapshot.count
                 )
             }
🧹 Nitpick comments (26)
src/Projects/BKCore/Project.swift (1)

15-17: BKCore에 FirebaseAnalytics 의존성 추가는 적절(LGTM) — 앱 타깃 중복 링크 정리 권장

BKCore에서만 의존하도록 두면 앱 타깃은 전이 의존성으로 충분합니다. 현재 Reed-Debug 타깃에도 직접 추가되어 있어 중복 링크가 될 수 있으니 정리 고려 바랍니다(Tuist가 중복을 합쳐주긴 합니다).

src/Projects/BKDesign/Sources/Components/BKPageControl.swift (1)

13-18: @objc dynamic 추가로 KVO/ObjC 호환 확보(LGTM) — currentPage 범위 클램프 제안

외부 바인딩에서 음수/초과 값이 들어올 수 있어 안전하게 표시하려면 내부에서 클램프를 권장합니다(재귀 방지 위해 렌더링 시 클램프).

다음처럼 updateIndicators에서 읽기 값을 클램프해 부작용 없이 안전하게 처리할 수 있습니다:

func updateIndicators() {
    guard numberOfPages > 0 else { return }
    let cp = max(0, min(currentPage, numberOfPages - 1))
    indicatorViews.enumerated().forEach { index, view in
        view.backgroundColor = index <= cp ? .bkBackgroundColor(.primary) : .bkBackgroundColor(.disable)
    }
}
src/Projects/Booket/Project.swift (1)

32-34: 디버그 타깃의 FirebaseAnalytics 직접 링크는 중복 — BKCore 전이 의존성으로 대체 가능

Reed-Debug가 .core()를 포함하므로 FirebaseAnalytics는 BKCore 경유로 링크됩니다. 직접 의존은 제거해도 됩니다.

아래와 같이 정리하면 중복 제거됩니다(기능 변화 없음).

         .external(dependency: .FirebaseCore),
-        .external(dependency: .FirebaseCrashlytics),
-        .external(dependency: .FirebaseAnalytics)
+        .external(dependency: .FirebaseCrashlytics)
src/Projects/BKPresentation/Sources/MainFlow/BookDetail/ViewModel/BookDetailViewModel.swift (1)

25-26: 레코드 삭제 완료 플래그/리셋 흐름 추가는 타당(LGTM) — 선택 ID 정리 한 줄 보완 제안

삭제 성공 시 selectedRecordId를 함께 초기화하면 상태 일관성이 좋아집니다.

아래처럼 보강을 제안드립니다:

         case .deleteRecordSuccessed:
             newState.isDeletingRecord = false
             newState.deleteRecordCompleted = true
+            newState.selectedRecordId = nil
             effects.append(.fetchRecords(page: 0))
             effects.append(.fetchSeedStats)

또한 액션 명 initDeleteRecordValueresetDeleteRecordCompleted 등 더 구체적 네이밍으로의 변경을 소소하게 권장드립니다(선택).

Also applies to: 57-58, 196-201, 212-214

src/Projects/BKCore/Sources/Tracking/GAManger.swift (2)

64-90: User Property 남용 주의(최대 25개 한도 및 값 길이 제한)

현재 14개 설정. 향후 확장 시 한도 초과 위험. time_zone·locale처럼 자주 변하는 값은 이벤트 파라미터로 옮기는 것도 고려하세요.

원하시면 공통 로거에서 “추가 파라미터” 인자로 받도록 리팩터 제안 드릴게요.


50-53: 권장: device_identifierdevice_model_code, getDeviceIdentifier()getDeviceModelCode()로 리네이밍 (파일 로컬, 안전)

rg 결과 해당 심볼들은 src/Projects/BKCore/Sources/Tracking/GAManger.swift 파일에만 존재합니다.

-        // 정확한 기기 식별자
-        let deviceIdentifier = getDeviceIdentifier()
-        Analytics.setUserProperty(deviceIdentifier, forName: "device_identifier")
+        // 하드웨어 모델 코드
+        let modelCode = getDeviceModelCode()
+        Analytics.setUserProperty(modelCode, forName: "device_model_code")
-    private static func getDeviceIdentifier() -> String {
+    private static func getDeviceModelCode() -> String {
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (2)

13-28: 스크린 이벤트 네이밍: 권장 이벤트(screen_view) 사용 권장

현재 이벤트명을 화면 이름으로 쓰고 있어 리포팅 표준화가 어렵습니다. GA4 권장 이벤트인 screen_view로 통일하고 파라미터에 이름/클래스를 담으세요.

-    public func logScreenView(name: String? = nil) {
-        if let name = name {
-            Analytics.logEvent(name,
-                               parameters: [
-                                AnalyticsParameterScreenName: name,
-                                AnalyticsParameterScreenClass: String(describing: type(of: self))
-                               ])
-        } else {
-            Analytics.logEvent(screenName,
-                               parameters: [
-                                AnalyticsParameterScreenName: screenName,
-                                AnalyticsParameterScreenClass: String(describing: type(of: self))
-                               ])
-        }
-    }
+    public func logScreenView(name: String? = nil) {
+        let resolved = name ?? screenName
+        Analytics.logEvent(AnalyticsEventScreenView, parameters: [
+            AnalyticsParameterScreenName: resolved,
+            AnalyticsParameterScreenClass: String(describing: type(of: self))
+        ])
+    }

31-47: 중복 로직 제거 및 이벤트명 통일

UIViewController가 아닌 타입에서도 동일 정책을 적용하세요. 이벤트명도 screen_view로 맞춰주세요.

-    public func logGoogleAnalytics(name: String? = nil) {
-        if let name = name {
-            Analytics.logEvent(name,
-                               parameters: [
-                                AnalyticsParameterScreenName: name,
-                                AnalyticsParameterScreenClass: String(describing: type(of: self))
-                               ])
-        } else {
-            Analytics.logEvent(screenName,
-                               parameters: [
-                                AnalyticsParameterScreenName: screenName,
-                                AnalyticsParameterScreenClass: String(describing: type(of: self))
-                               ])
-        }
-    }
+    public func logGoogleAnalytics(name: String? = nil) {
+        let resolved = name ?? screenName
+        Analytics.logEvent(AnalyticsEventScreenView, parameters: [
+            AnalyticsParameterScreenName: resolved,
+            AnalyticsParameterScreenClass: String(describing: type(of: self))
+        ])
+    }
src/Projects/BKPresentation/Sources/OnboardingFlow/View/OnboardingViewController.swift (1)

12-13: 불변 프로퍼티로 선언

screenName은 변경되지 않으므로 let으로 두는 편이 명확합니다.

-    var screenName: String = GATracking.OnboardingAndAuth.onboarding
+    let screenName: String = GATracking.OnboardingAndAuth.onboarding
src/Projects/BKPresentation/Sources/MainFlow/Note/View/OCRScannerViewController.swift (1)

11-12: 불변 프로퍼티로 선언

변경되지 않는 값은 let 권장.

-    var screenName: String = GATracking.RecordFlow.ocrCamera
+    let screenName: String = GATracking.RecordFlow.ocrCamera
src/Projects/BKPresentation/Sources/ArchiveFlow/View/ArchiveViewController.swift (1)

16-18: 불변 프로퍼티로 선언

screenName은 상수로 두는 편이 안전합니다.

-    var screenName: String = GATracking.HomeAndLibrary.libraryMain
+    let screenName: String = GATracking.HomeAndLibrary.libraryMain
src/Projects/BKPresentation/Sources/MainFlow/NoteEdit/View/NoteEditViewController.swift (1)

9-11: screenName는 불변 값이면 let으로 고정 권장

변경 가능성이 없다면 불변으로 두어 의도 표현과 오용 방지를 겸하세요.

적용 diff:

-    var screenName: String = GATracking.RecordFlow.edit
+    let screenName: String = GATracking.RecordFlow.edit
src/Projects/BKPresentation/Sources/AuthFlow/View/LoginViewController.swift (1)

71-71: 에러는 screen_view가 아닌 이벤트 로거로 분리 권장

오류는 화면 노출과 다른 성격의 이벤트입니다. 가능하다면 전용 이벤트 로거 API로 기록하세요.

적용 diff:

-                self?.logScreenView(name: GATracking.Error.login)
+                self?.logGoogleAnalytics(name: GATracking.Error.login)
src/Projects/BKPresentation/Sources/MainFlow/Setting/View/SettingViewController.swift (1)

16-18: screenName 불변이면 let으로 변경 제안

의도 전달과 실수 예방을 위해 상수로 두는 편이 좋습니다.

적용 diff:

-    var screenName: String = GATracking.Settings.main
+    let screenName: String = GATracking.Settings.main
src/Projects/BKPresentation/Sources/MainFlow/BookDetail/View/SentenceCardViewController.swift (1)

60-60: 버튼 액션·시트 표시 등 상호작용은 이벤트 로거 사용 권장

스크린 노출과 구분해 이벤트(저장/공유)는 전용 이벤트 API로 기록하면 분석 체계가 명확해집니다.

적용 diff:

-                    self.logScreenView(name: GATracking.RecordCard.save)
+                    self.logGoogleAnalytics(name: GATracking.RecordCard.save)
-        self.logScreenView(name: GATracking.RecordCard.share)
+        self.logGoogleAnalytics(name: GATracking.RecordCard.share)

추가 제안(선택): 공유 성공/취소 구분 로깅

activityViewController.completionWithItemsHandler = { [weak self] activityType, completed, _, _ in
    if completed {
        self?.logGoogleAnalytics(name: GATracking.RecordCard.shareComplete)
    } else {
        self?.logGoogleAnalytics(name: GATracking.RecordCard.shareCancel)
    }
}

Also applies to: 138-139

src/Projects/BKPresentation/Sources/MainFlow/Note/View/NoteViewController.swift (1)

239-241: 가이드 표시 이벤트도 이벤트 로거로 분리 권장

화면 노출과 구분을 위해 전용 이벤트 로거 사용을 추천드립니다.

적용 diff:

-        logScreenView(name: GATracking.RecordFlow.inputHelp)
+        logGoogleAnalytics(name: GATracking.RecordFlow.inputHelp)
src/Projects/BKPresentation/Sources/MainFlow/Home/View/HomeViewController.swift (1)

16-18: screenName는 불변이므로 let으로 선언해 주세요

프로퍼티가 변경되지 않는다면 상수로 두는 편이 안전합니다.

다음과 같이 변경 제안:

-final class HomeViewController: BaseViewController<HomeView>, ScreenLoggable {
-    var screenName: String = GATracking.HomeAndLibrary.homeMain
+final class HomeViewController: BaseViewController<HomeView>, ScreenLoggable {
+    let screenName: String = GATracking.HomeAndLibrary.homeMain
src/Projects/BKPresentation/Sources/MainFlow/NoteCompletion/View/NoteCompletionViewController.swift (1)

8-10: screenName는 let으로 고정 권장

변경되지 않는 값이므로 상수 권장.

-final class NoteCompletionViewController: BaseViewController<NoteCompletionView>, ScreenLoggable {
-    var screenName: String = GATracking.RecordFlow.detail
+final class NoteCompletionViewController: BaseViewController<NoteCompletionView>, ScreenLoggable {
+    let screenName: String = GATracking.RecordFlow.detail
src/Projects/BKPresentation/Sources/MainFlow/BookDetail/View/BookDetailViewController.swift (2)

18-20: 불변 screenName 상수화 제안

다른 VC와 동일하게 let 사용 권장.

-final class BookDetailViewController: BaseViewController<BookDetailView>, ScreenLoggable {
-    var screenName: String = GATracking.HomeAndLibrary.bookDetail
+final class BookDetailViewController: BaseViewController<BookDetailView>, ScreenLoggable {
+    let screenName: String = GATracking.HomeAndLibrary.bookDetail

223-225: logScreenView 대신 이벤트 전용 헬퍼 사용 권장

확인: src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift의 public func logScreenView(name:)가 전달된 name을 Analytics.logEvent(name, parameters: ...)로 바로 호출하고 있어(스크린 로그 API와 이벤트가 혼용됨) 의미가 모호합니다. 스크린 노출과 사용자 액션 이벤트는 분리하세요.

제안:

  • 변경(간단 diff)
-                self?.logScreenView(name: GATracking.HomeAndLibrary.deleteBookComplete)
+                self?.logEvent(GATracking.HomeAndLibrary.deleteBookComplete)
  • ScreenLoggable에 추가할 이벤트 헬퍼:
func logEvent(_ name: String, params: [String: Any]? = nil) {
    var parameters: [String: Any] = [
        AnalyticsParameterScreenName: screenName,
        AnalyticsParameterScreenClass: String(describing: type(of: self))
    ]
    params?.forEach { parameters[$0.key] = $0.value }
    Analytics.logEvent(name, parameters: parameters)
}
src/Projects/BKPresentation/Sources/MainFlow/Search/ViewModel/SearchViewModel.swift (2)

95-96: 불필요한 옵셔널 초기화 제거

SwiftLint 경고(redundant_optional_initialization) 해소를 위해 nil 초기화 제거 권장.

-        var viewType: SearchViewType? = nil
+        var viewType: SearchViewType?

165-170: viewType는 변경되지 않으므로 State에서 비-옵셔널 상수로 관리해 단순화

초기화 시 항상 값이 주입되므로 옵셔널/가드 로직을 제거할 수 있습니다. 이후 VC 코드도 간결해집니다.

-    struct State: Equatable {
+    struct State: Equatable {
         ...
-        var viewType: SearchViewType?
+        let viewType: SearchViewType
     }

생성부는 그대로 동작합니다:

-        self.state = State(
+        self.state = State(
             searchBarPlaceholder: searchViewType.searchBarPlaceholder,
             searchViewTitle: searchViewType.searchViewTitle,
             viewType: searchViewType
         )

그리고 SearchViewController에서 switch의 default 분기를 제거할 수 있습니다(아래 해당 파일 코멘트 참고).

src/Projects/BKPresentation/Sources/MainFlow/Search/View/SearchViewController.swift (4)

16-18: 빈 screenName 사용은 잠재적 리스크 — 의미 있는 기본값 지정 또는 주석 명시

현 파일에서는 logScreenView() 기본 호출이 없지만, 추후 호출 시 빈 이벤트명이 기록될 수 있습니다. 의미 있는 기본값(예: 검색 화면 식별 키)으로 지정하거나 “의도적으로 비워둠” 주석을 남겨 주세요.

-final class SearchViewController: BaseViewController<SearchView>, ScreenLoggable {
-    var screenName: String = ""
+final class SearchViewController: BaseViewController<SearchView>, ScreenLoggable {
+    // NOTE: 이 화면은 개별 이벤트 로깅만 사용. 기본 스크린 로깅 방지를 위해 명시적 키 사용 권장.
+    let screenName: String = GATracking.SearchAndRegister.searchMain // 적절한 키로 교체

126-141: 뷰 타입 전환 시 시작 로그 처리 적절 — 옵셔널 제거 시 코드 단순화 가능

현재도 동작 문제 없으나, viewType을 비-옵셔널로 만들면 default 분기 제거 가능.

-            .map { $0.viewType }
+            .map { $0.viewType }
             .removeDuplicates()
             .sink { [weak self] type in
-                switch type {
-                case .defaultSearch:
+                switch type {
+                case .defaultSearch:
                     self?.logScreenView(name: GATracking.SearchAndRegister.start)
                 case .myLibrarySearch:
                     self?.logScreenView(name: GATracking.HomeAndLibrary.searchBook)
-                default:
-                    return
                 }
             }

209-221: 에러 로깅 라인은 주석 유지 vs. 플래그화 중 선택 필요

주석 상태가 길어지면 혼란을 유발합니다. 빌드 플래그(예: ENABLE_GA_ERROR_LOG)나 Remote Config로 토글 가능하게 하거나, 필요 없다면 제거를 권장합니다.


229-241: 2차 재시도 에러 경로도 동일 정책 적용

위와 동일한 토글/정리 전략을 권장합니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eab1f20 and cfb662c.

📒 Files selected for processing (26)
  • src/Projects/BKCore/Project.swift (1 hunks)
  • src/Projects/BKCore/Sources/Tracking/GAManger.swift (1 hunks)
  • src/Projects/BKCore/Sources/Tracking/GATracking.swift (1 hunks)
  • src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1 hunks)
  • src/Projects/BKDesign/Sources/Components/BKPageControl.swift (1 hunks)
  • src/Projects/BKPresentation/Sources/AppCoordinator.swift (2 hunks)
  • src/Projects/BKPresentation/Sources/ArchiveFlow/View/ArchiveViewController.swift (2 hunks)
  • src/Projects/BKPresentation/Sources/AuthFlow/View/LoginViewController.swift (3 hunks)
  • src/Projects/BKPresentation/Sources/AuthFlow/View/TermsViewController.swift (2 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/BookDetail/View/BookDetailViewController.swift (5 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/BookDetail/View/SentenceCardViewController.swift (5 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/BookDetail/ViewModel/BookDetailViewModel.swift (4 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/Home/View/HomeViewController.swift (3 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/Note/View/NoteViewController.swift (5 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/Note/View/OCRScannerViewController.swift (2 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/Note/View/RecognizedTextViewController.swift (2 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/NoteCompletion/View/NoteCompletionViewController.swift (2 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/NoteEdit/View/NoteEditViewController.swift (2 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/Search/View/SearchViewController.swift (7 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/Search/ViewModel/SearchViewModel.swift (2 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/Setting/View/SettingViewController.swift (5 hunks)
  • src/Projects/BKPresentation/Sources/OnboardingFlow/View/OnboardingViewController.swift (2 hunks)
  • src/Projects/Booket/Project.swift (2 hunks)
  • src/Projects/Booket/Sources/AppDelegate.swift (2 hunks)
  • src/SupportingFiles/Booket/Info.plist (1 hunks)
  • src/Tuist/Package.swift (0 hunks)
💤 Files with no reviewable changes (1)
  • src/Tuist/Package.swift
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: doyeonk429
PR: YAPP-Github/Reed-iOS#157
File: src/Projects/BKPresentation/Sources/MainFlow/Home/View/HomeViewController.swift:19-24
Timestamp: 2025-08-08T01:38:59.656Z
Learning: doyeonk429는 Reed-iOS 프로젝트에서 접근성(accessibility) 관련 개선사항은 현재 작업 중인 PR에서 즉시 처리하지 않고, 접근성 전용 PR이나 이슈를 별도로 만들어 한번에 처리하는 것을 선호한다.
📚 Learning: 2025-07-07T13:47:32.784Z
Learnt from: clxxrlove
PR: YAPP-Github/Reed-iOS#56
File: src/Projects/BKPresentation/Sources/AuthFlow/ViewModel/LoginViewModel.swift:109-111
Timestamp: 2025-07-07T13:47:32.784Z
Learning: The LoginViewModel's authenticateWithToken case in src/Projects/BKPresentation/Sources/AuthFlow/ViewModel/LoginViewModel.swift is test code and not important for implementation according to the user.

Applied to files:

  • src/Projects/BKPresentation/Sources/AuthFlow/View/LoginViewController.swift
📚 Learning: 2025-08-01T22:35:20.411Z
Learnt from: clxxrlove
PR: YAPP-Github/Reed-iOS#136
File: src/Projects/BKPresentation/Sources/MainFlow/Home/View/HomeViewController.swift:47-48
Timestamp: 2025-08-01T22:35:20.411Z
Learning: clxxrlove는 HomeViewController에서 didTapBook 케이스의 bookId 파라미터 활용을 API 구현 완료 후로 연기한다고 명시했다. API와 화면 연결 시점에 한번에 처리하는 것을 선호하는 개발 방식을 보여준다.

Applied to files:

  • src/Projects/BKPresentation/Sources/MainFlow/Home/View/HomeViewController.swift
📚 Learning: 2025-08-08T11:20:33.637Z
Learnt from: clxxrlove
PR: YAPP-Github/Reed-iOS#160
File: src/Projects/BKPresentation/Sources/Common/Coordinator/ErrorHandleable.swift:9-20
Timestamp: 2025-08-08T11:20:33.637Z
Learning: Reed-iOS: ErrorHandleable.handleError는 API를 사용하는 View용이며, unauthorized 발생 시 세션 만료 흐름을 보장하기 위해 Self: Coordinator & SessionExpirationNotifying 제약을 의도적으로 요구한다.

Applied to files:

  • src/Projects/BKPresentation/Sources/MainFlow/Search/View/SearchViewController.swift
🧬 Code graph analysis (18)
src/Projects/BKPresentation/Sources/MainFlow/Note/View/OCRScannerViewController.swift (1)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
src/Projects/BKPresentation/Sources/MainFlow/Note/View/RecognizedTextViewController.swift (1)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
src/Projects/BKPresentation/Sources/OnboardingFlow/View/OnboardingViewController.swift (3)
src/Projects/BKPresentation/Sources/AuthFlow/View/LoginViewController.swift (1)
  • viewDidAppear (37-40)
src/Projects/BKPresentation/Sources/AuthFlow/View/TermsViewController.swift (1)
  • viewDidAppear (32-35)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
src/Projects/BKPresentation/Sources/AuthFlow/View/LoginViewController.swift (4)
src/Projects/BKPresentation/Sources/MainFlow/Setting/View/SettingViewController.swift (1)
  • viewDidAppear (48-51)
src/Projects/BKPresentation/Sources/AuthFlow/View/TermsViewController.swift (1)
  • viewDidAppear (32-35)
src/Projects/BKPresentation/Sources/OnboardingFlow/View/OnboardingViewController.swift (1)
  • viewDidAppear (34-37)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
src/Projects/BKCore/Project.swift (1)
src/Tuist/ProjectDescriptionHelpers/TargetDependency+External.swift (1)
  • external (32-34)
src/Projects/Booket/Sources/AppDelegate.swift (1)
src/Projects/BKCore/Sources/Tracking/GAManger.swift (1)
  • configureAnalytics (8-35)
src/Projects/Booket/Project.swift (1)
src/Tuist/ProjectDescriptionHelpers/TargetDependency+External.swift (1)
  • external (32-34)
src/Projects/BKPresentation/Sources/MainFlow/NoteEdit/View/NoteEditViewController.swift (1)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
src/Projects/BKPresentation/Sources/MainFlow/Setting/View/SettingViewController.swift (1)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
src/Projects/BKPresentation/Sources/MainFlow/Note/View/NoteViewController.swift (3)
src/Projects/BKPresentation/Sources/MainFlow/Note/View/SentenceRegistrationView.swift (1)
  • setScannedText (162-165)
src/Projects/BKPresentation/Sources/MainFlow/Note/View/NoteView.swift (2)
  • setScannedText (348-356)
  • handleEvent (338-345)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
src/Projects/BKPresentation/Sources/ArchiveFlow/View/ArchiveViewController.swift (2)
src/Projects/BKPresentation/Sources/MainFlow/Home/View/HomeViewController.swift (1)
  • viewDidAppear (55-58)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
src/Projects/BKPresentation/Sources/AppCoordinator.swift (1)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logGoogleAnalytics (32-46)
src/Projects/BKPresentation/Sources/AuthFlow/View/TermsViewController.swift (6)
src/Projects/BKPresentation/Sources/MainFlow/Setting/View/SettingViewController.swift (1)
  • viewDidAppear (48-51)
src/Projects/BKPresentation/Sources/MainFlow/BookDetail/View/BookDetailViewController.swift (1)
  • viewDidAppear (57-60)
src/Projects/BKPresentation/Sources/MainFlow/Home/View/HomeViewController.swift (1)
  • viewDidAppear (55-58)
src/Projects/BKPresentation/Sources/AuthFlow/View/LoginViewController.swift (1)
  • viewDidAppear (37-40)
src/Projects/BKPresentation/Sources/OnboardingFlow/View/OnboardingViewController.swift (1)
  • viewDidAppear (34-37)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
src/Projects/BKPresentation/Sources/MainFlow/Home/View/HomeViewController.swift (5)
src/Projects/BKPresentation/Sources/MainFlow/Setting/View/SettingViewController.swift (1)
  • viewDidAppear (48-51)
src/Projects/BKPresentation/Sources/MainFlow/BookDetail/View/BookDetailViewController.swift (1)
  • viewDidAppear (57-60)
src/Projects/BKPresentation/Sources/ArchiveFlow/View/ArchiveViewController.swift (1)
  • viewDidAppear (50-53)
src/Projects/BKPresentation/Sources/AuthFlow/View/TermsViewController.swift (1)
  • viewDidAppear (32-35)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
src/Projects/BKPresentation/Sources/MainFlow/BookDetail/View/BookDetailViewController.swift (2)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
src/Projects/BKPresentation/Sources/MainFlow/BookDetail/ViewModel/BookDetailViewModel.swift (1)
  • send (96-100)
src/Projects/BKPresentation/Sources/MainFlow/Search/View/SearchViewController.swift (2)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
src/Projects/BKPresentation/Sources/MainFlow/Search/Coordinator/SearchCoordinator.swift (1)
  • start (22-30)
src/Projects/BKPresentation/Sources/MainFlow/NoteCompletion/View/NoteCompletionViewController.swift (1)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
src/Projects/BKPresentation/Sources/MainFlow/BookDetail/View/SentenceCardViewController.swift (3)
src/Projects/BKPresentation/Sources/MainFlow/Note/View/RecognizedTextViewController.swift (1)
  • viewDidAppear (58-61)
src/Projects/BKPresentation/Sources/MainFlow/NoteEdit/View/NoteEditViewController.swift (1)
  • viewDidAppear (48-51)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-28)
🪛 SwiftLint (0.57.0)
src/Projects/BKPresentation/Sources/MainFlow/Search/ViewModel/SearchViewModel.swift

[Warning] 95-95: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (31)
src/SupportingFiles/Booket/Info.plist (1)

93-94: 자동 스크린 리포팅 비활성화(LGTM) — 수동 로깅 전환과 일치

FirebaseAutomaticScreenReportingEnabled=false 설정은 이번 PR의 수동 스크린 로깅 전략과 일관됩니다. 동일 Info.plist를 Debug/Release가 공유하므로 구성별 누락 리스크도 낮습니다.

  • 수동 스크린 로깅(ScreenLoggable)에서 viewDidAppear 등 생명주기 재진입 시 중복 로깅이 없는지 한 번 점검 부탁드립니다.
  • GA/ATT 관련: 현재 FirebaseAnalytics만 링크되어 있고 AdSupport를 쓰지 않으면 ATT(NSUserTrackingUsageDescription) 필수는 아닙니다. 추후 광고 SDK 연동 시에는 반드시 추가해 주세요.
src/Projects/Booket/Project.swift (1)

105-110: DebugView 활성화 플래그 적용(LGTM)

-FIRAnalyticsDebugEnabled 런치 인자 사용이 적절하며 디버그 스킴에만 한정되어 안전합니다.

src/Projects/BKCore/Sources/Tracking/GAManger.swift (1)

9-15: DEBUG 빌드에서 Analytics 수집을 끄지 마세요 — 수집은 켜고 DebugView로 검증

File: src/Projects/BKCore/Sources/Tracking/GAManger.swift (lines 9–15)
DEBUG에서 전면 수집 비활성화하면 시뮬레이터/GA4 DebugView에서 태그 확인이 불가능합니다. 디버그 검증은 런치 아규먼트 '-FIRAnalyticsDebugEnabled'로 처리하세요.

-#if DEBUG
-    Analytics.setAnalyticsCollectionEnabled(false)
-    Analytics.setUserProperty("true", forName: "debug_mode")
-#else
-    Analytics.setAnalyticsCollectionEnabled(true)
-    Analytics.setUserProperty("false", forName: "debug_mode")
-#endif
+Analytics.setAnalyticsCollectionEnabled(true)

디버그 기기에서 DebugView에 디바이스가 표시되는지 확인하고 결과 공유 (Xcode Scheme → Arguments에 '-FIRAnalyticsDebugEnabled' 추가).

src/Projects/BKCore/Sources/Tracking/GATracking.swift (1)

43-45: GA 이벤트 키 소문자화: record_OCR_ → record_ocr_로 통일

rg 결과: 'record_OCR_camera'와 'record_OCR_sentence'는 src/Projects/BKCore/Sources/Tracking/GATracking.swift 43–44행에만 존재합니다.

-        public static let ocrCamera = "record_OCR_camera"
-        public static let ocrSentence = "record_OCR_sentence"
+        public static let ocrCamera = "record_ocr_camera"
+        public static let ocrSentence = "record_ocr_sentence"

내부 코드 참조는 없어 코드 내 전역 치환은 불필요합니다. 단, GA 이벤트 키 변경은 외부 분석/대시보드·데이터 이력에 영향이 있으므로 관련 설정·이력 매핑을 함께 업데이트하세요.

src/Projects/BKPresentation/Sources/OnboardingFlow/View/OnboardingViewController.swift (1)

34-37: LGTM — 화면 표시에 맞춘 viewDidAppear 로깅 적절

표준화된 스크린 로깅 패턴에 부합합니다.

src/Projects/BKPresentation/Sources/MainFlow/Note/View/OCRScannerViewController.swift (1)

103-106: LGTM — 스캐너 화면 진입 시 스크린 로깅 적절

성능/동작 영향 없음.

src/Projects/BKPresentation/Sources/AppCoordinator.swift (2)

10-12: LGTM — 코디네이터에서 스플래시 스크린 로깅 초기 1회 호출

앱 흐름 시작 시점 로깅으로 충분합니다.


37-39: LGTM — start()에서 선로깅 후 업데이트 체크

후속 플로우와 충돌 없음.

src/Projects/BKPresentation/Sources/ArchiveFlow/View/ArchiveViewController.swift (1)

50-54: LGTM — 화면 표시 시점(viewDidAppear) 로깅 패턴 일관

기존 Home과 동일 패턴으로 문제 없습니다.

src/Projects/BKPresentation/Sources/MainFlow/NoteEdit/View/NoteEditViewController.swift (1)

48-51: 화면 노출 시점(viewDidAppear) 로깅 적용 적절

표준적인 화면 노출 시점에 스크린 로그를 남기도록 구성되어 있습니다.

src/Projects/BKPresentation/Sources/MainFlow/Note/View/RecognizedTextViewController.swift (2)

9-10: ScreenLoggable 채택 및 screenName 지정 적정

해당 플로우 키(ocrSentence) 매핑이 명확합니다.


58-61: viewDidAppear 로깅 훅 추가 LGTM

중복 로깅 우려 없는 안전한 위치입니다.

src/Projects/BKPresentation/Sources/AuthFlow/View/LoginViewController.swift (3)

3-3: BKCore import 추가 적절

추가된 트래킹 유틸 사용을 위한 의존성 반영입니다.


10-11: ScreenLoggable 도입 및 화면 키 지정 OK

온보딩/인증 플로우 진입 화면으로 selectMethod 키 사용 타당합니다.


37-41: 화면 로깅 훅(viewDidAppear) 추가 OK

일관된 패턴 유지되어 좋습니다.

src/Projects/BKPresentation/Sources/AuthFlow/View/TermsViewController.swift (2)

9-10: ScreenLoggable 채택 및 termsAgreement 매핑 적절

GA 키 네이밍과 화면 목적이 일치합니다.


32-35: viewDidAppear 시점 스크린 로깅 OK

탐색바 숨김 처리 흐름과 충돌 없음 확인.

src/Projects/BKPresentation/Sources/MainFlow/Setting/View/SettingViewController.swift (2)

48-51: 화면 로깅(viewDidAppear) 적용 적절

설정 메인 진입 시점 로깅 패턴 일관성 OK.


166-166: 탈퇴 경고 노출 시점 로그 OK

사용자 주의 환기 이벤트로 경고 노출 시점 기록은 타당합니다.

src/Projects/BKPresentation/Sources/MainFlow/BookDetail/View/SentenceCardViewController.swift (3)

3-3: BKCore import 추가 적절

트래킹 유틸 사용 전제 충족.


14-16: ScreenLoggable 채택 및 화면 키 매핑 OK

RecordCard.main 스크린 키 지정 합리적입니다.


46-49: 화면 로깅(viewDidAppear) 적용 OK

중복/누락 우려 없음.

src/Projects/BKPresentation/Sources/MainFlow/Note/View/NoteViewController.swift (3)

3-3: BKCore import 추가 OK

트래킹 유틸 사용을 위한 의존성 반영입니다.


168-179: 페이지 지표 매핑 명확함

문장/감정/감상 입력 단계 구분 로깅이 명확합니다.


193-193: 완료 시점 로깅 위치 적절

createCompleted 신호 처리 분기 내에서 기록되어 정확합니다.

src/Projects/BKPresentation/Sources/MainFlow/Home/View/HomeViewController.swift (1)

55-58: 화면 노출 시점의 스크린 로그 처리 적절

viewDidAppear에서 1회 로깅하는 패턴 일관성 있고 👍

src/Projects/BKPresentation/Sources/MainFlow/NoteCompletion/View/NoteCompletionViewController.swift (1)

56-59: 스크린 뷰 로깅 위치 적절

viewDidAppear에서의 호출 타이밍 적합합니다.

src/Projects/BKPresentation/Sources/MainFlow/BookDetail/View/BookDetailViewController.swift (1)

57-60: 스크린 뷰 로깅 OK

표시 완료 시점(viewDidAppear) 로깅 패턴 일관 유지되어 좋습니다.

src/Projects/BKPresentation/Sources/MainFlow/Search/View/SearchViewController.swift (3)

21-23: 타이틀 동적 바인딩으로 이관 OK

고정 문자열 제거하고 상태 기반으로 설정하는 변경 적합합니다.


257-258: 등록 옵션 선택 시점 로깅 적절

사용자 선택 직후 로깅 타이밍 적합합니다.


282-283: 등록 완료(노트 제안) 로깅 타이밍 적절

성공 플로우 완료 지점에서의 로깅 위치 좋습니다.

Comment on lines 153 to 160
contentView.pageControl.publisher(for: \.currentPage)
.removeDuplicates()
.receive(on: DispatchQueue.main)
.sink { [weak self] currentPage in
self?.logPageView(for: currentPage)
}
.store(in: &cancellable)
}
Copy link
Member

Choose a reason for hiding this comment

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

이 코드 지금은 Approve하고 Merge할건데, 이후 리팩토링 대상입니다 !

Copy link
Member

Choose a reason for hiding this comment

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

  • bindAction()bindState() 구분해주세요.

Comment on lines +16 to +20
Analytics.logEvent(name,
parameters: [
AnalyticsParameterScreenName: name,
AnalyticsParameterScreenClass: String(describing: type(of: self))
])
Copy link
Member

Choose a reason for hiding this comment

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

이렇게 개행하는 거 수정할게요.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/Projects/BKPresentation/Sources/MainFlow/Note/View/NoteViewController.swift (1)

79-86: UIPageControl.currentPage KVO 사용 중단 필요 — 크래시/비보장

UIPageControl.currentPage는 KVO 보장 대상이 아닙니다. 런타임 실패/크래시 위험이 있어 .valueChanged 타깃-액션으로 전환해야 합니다. TODO도 함께 제거하세요.

KVO 구독 제거:

-        // TODO: - Remove KVO Binding
-        contentView.pageControl.publisher(for: \.currentPage)
-            .removeDuplicates()
-            .receive(on: DispatchQueue.main)
-            .sink { [weak self] currentPage in
-                self?.logPageView(for: currentPage)
-            }
-            .store(in: &cancellable)

대신 1회 설정:

override func viewDidLoad() {
    super.viewDidLoad()
    contentView.pageControl.addTarget(self, action: #selector(pageControlChanged(_:)), for: .valueChanged)
}

@objc private func pageControlChanged(_ sender: UIPageControl) {
    logPageView(for: sender.currentPage)
}
🧹 Nitpick comments (12)
src/Projects/BKPresentation/Sources/MainFlow/Note/View/NoteViewController.swift (5)

16-17: ScreenLoggable 채택 및 기본 screenName 지정 좋습니다

이전 빈 문자열 이슈 해소됐습니다. 불변이면 let으로 선언해 의도 고정 권장.

적용 diff:

-    var screenName: String = GATracking.RecordFlow.start
+    let screenName: String = GATracking.RecordFlow.start

16-17: 초기 진입 화면 노출 로그 경로 확인 필요

해당 화면 최초 진입 시 1회 logScreenView() 호출 경로가 있는지 점검해주세요(자동 훅이 없다면 viewDidAppear에서 호출 권장).

예시:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    logScreenView() // screenName 사용
}

183-191: 프로그램적 페이지 이동 시에도 로깅 보장

back 버튼에서 currentPage를 직접 감소시키므로 .valueChanged가 발동하지 않습니다. 감소 후 수동으로 로그 호출 필요.

적용 diff:

-            contentView.pageControl.currentPage -= 1
+            contentView.pageControl.currentPage -= 1
+            logPageView(for: contentView.pageControl.currentPage)

168-180: logPageView 매핑 OK — 경계 가드로 사소한 견고성 강화 제안

스위치 대신 경계 가드+배열 인덱싱으로 축약 가능. 선택사항입니다.

예시:

-    private func logPageView(for pageIndex: Int) {
-        switch pageIndex {
-        case 0:
-            logScreenView(name: GATracking.RecordFlow.inputSentence)
-        case 1:
-            logScreenView(name: GATracking.RecordFlow.selectEmotion)
-        case 2:
-            logScreenView(name: GATracking.RecordFlow.inputOpinion)
-        default:
-            break
-        }
-    }
+    private func logPageView(for pageIndex: Int) {
+        guard (0...2).contains(pageIndex) else { return }
+        let keys = [
+            GATracking.RecordFlow.inputSentence,
+            GATracking.RecordFlow.selectEmotion,
+            GATracking.RecordFlow.inputOpinion
+        ]
+        logScreenView(name: keys[pageIndex])
+    }

241-242: 가이드 시트 진입 로그 OK — 사용자 상호작용 로그도 고려

가이드 선택/취소 액션에 대한 이벤트(logEvent)도 추가하면 퍼널 분석에 유용합니다.

예시:

confirmAction: { [weak self] selectedGuide in
    self?.logScreenView(name: GATracking.RecordFlow.inputHelpConfirm) // 신규 키
    self?.viewModel.send(.appreciationGuideSelected(selectedGuide.rawValue))
    self?.dismiss(animated: true)
}
// 시트 닫힘/취소 시:
logScreenView(name: GATracking.RecordFlow.inputHelpCancel)
src/Projects/BKCore/Sources/Tracking/GAManager.swift (6)

17-22: 시뮬레이터 식별은 OK이나 ‘사용자 속성’보다 ‘기본 이벤트 파라미터’가 적합

세션 전역 사용자 특성으로 남길 필요가 없다면 setUserProperty 대신 setDefaultEventParameters(["simulator": "true/false"])가 더 가볍고 분석에 충분합니다. 사용자 속성 슬롯(최대치) 소모 방지도 됩니다.


24-28: app_version는 GA4가 기본 수집 — 중복/형식 통일 권장

GA4가 이벤트 파라미터로 app_version을 기본 수집합니다. 사용자 속성으로도 꼭 필요하다면 형식을 일반적으로 쓰는 "version (build)"로 통일하세요.

제안 패치:

-Analytics.setUserProperty("\(version)(\(build))", forName: "app_version")
+Analytics.setUserProperty("\(version) (\(build))", forName: "app_version")

또는 기본 이벤트 파라미터 사용:

Analytics.setDefaultEventParameters(["app_version": "\(version) (\(build))"])

51-58: 해상도 단위 일관성: points 대신 픽셀(native) 고려

UIScreen.main.bounds.size는 pt 단위입니다. 분석에 실제 해상도가 필요하면 nativeBounds/nativeScale 사용이 더 일관됩니다.

제안 패치:

- let screenSize = UIScreen.main.bounds.size
- let screenSizeString = "\(Int(screenSize.width))x\(Int(screenSize.height))"
- Analytics.setUserProperty(screenSizeString, forName: "screen_size")
- let screenScale = UIScreen.main.scale
- Analytics.setUserProperty("\(screenScale)", forName: "screen_scale")
+ let nativeBounds = UIScreen.main.nativeBounds
+ let nativeScale = UIScreen.main.nativeScale
+ let pxSize = "\(Int(nativeBounds.width))x\(Int(nativeBounds.height))"
+ Analytics.setUserProperty(pxSize, forName: "screen_size_px")
+ Analytics.setUserProperty("\(nativeScale)", forName: "screen_scale")

73-86: 위치·타임존·국가명 사용자 속성은 축약 권장

country_code만으로도 대부분 분석 가능하며, country_name은 로캘에 따라 값이 길거나 가변적입니다(집계 키로 부적합). 기본 자동 수집과의 중복도 검토 바랍니다.


88-97: utsname 파싱 간소화 가능(선택)

Mirror 대신 CChar 버퍼를 UTF‑8로 변환하면 더 단순합니다. 성능 차이는 미미하여 선택 사항입니다.

예:

private static func getDeviceIdentifier() -> String {
    var info = utsname()
    uname(&info)
    return withUnsafePointer(to: &info.machine) {
        $0.withMemoryRebound(to: CChar.self, capacity: 1) { ptr in
            String(validatingUTF8: ptr) ?? "unknown"
        }
    }
}

7-98: 정적 유틸리티는 enum 무인스턴스화 패턴이 더 안전

인스턴스화가 불필요하므로 final class 대신 enum(case 없음)으로 전환하면 실수로 인스턴스를 만들 여지를 없앨 수 있습니다.

제안 패치(선언부):

-public final class GAManager {
+public enum GAManager {
src/Projects/Booket/Sources/AppDelegate.swift (1)

28-28: 초기화 위치는 적절하나, 동의 수집 후 활성화로 전환 권장

FirebaseApp.configure() 직후 호출 순서는 적절합니다. 다만 릴리스 빌드에선 UMP 등으로 사용자 동의를 받은 뒤 GAManager.applyConsent(...)로 수집을 활성화하도록 연결해 주세요.

동의 플로우(예: UMP)와 연동 계획이 있으면 공유 부탁드립니다. 필요 시 샘플 코드 제공 가능합니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfb662c and b524a84.

📒 Files selected for processing (6)
  • src/Projects/BKCore/Sources/Tracking/GAManager.swift (1 hunks)
  • src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/Note/View/NoteViewController.swift (6 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/Setting/View/SettingViewController.swift (5 hunks)
  • src/Projects/BKPresentation/Sources/MainFlow/Setting/ViewModel/SettingViewModel.swift (0 hunks)
  • src/Projects/Booket/Sources/AppDelegate.swift (2 hunks)
💤 Files with no reviewable changes (1)
  • src/Projects/BKPresentation/Sources/MainFlow/Setting/ViewModel/SettingViewModel.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/Projects/BKPresentation/Sources/MainFlow/Setting/View/SettingViewController.swift
  • src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: doyeonk429
PR: YAPP-Github/Reed-iOS#157
File: src/Projects/BKPresentation/Sources/MainFlow/Home/View/HomeViewController.swift:19-24
Timestamp: 2025-08-08T01:38:59.656Z
Learning: doyeonk429는 Reed-iOS 프로젝트에서 접근성(accessibility) 관련 개선사항은 현재 작업 중인 PR에서 즉시 처리하지 않고, 접근성 전용 PR이나 이슈를 별도로 만들어 한번에 처리하는 것을 선호한다.
🧬 Code graph analysis (2)
src/Projects/Booket/Sources/AppDelegate.swift (1)
src/Projects/BKCore/Sources/Tracking/GAManager.swift (1)
  • configureAnalytics (8-35)
src/Projects/BKPresentation/Sources/MainFlow/Note/View/NoteViewController.swift (3)
src/Projects/BKPresentation/Sources/AppCoordinator.swift (1)
  • start (37-40)
src/Projects/BKPresentation/Sources/MainFlow/Note/Coordinator/NoteCoordinator.swift (1)
  • start (29-37)
src/Projects/BKCore/Sources/Tracking/ScreenLoggable.swift (1)
  • logScreenView (14-32)
🪛 SwiftLint (0.57.0)
src/Projects/BKPresentation/Sources/MainFlow/Note/View/NoteViewController.swift

[Warning] 79-79: TODOs should be resolved (- Remove KVO Binding)

(todo)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (4)
src/Projects/BKPresentation/Sources/MainFlow/Note/View/NoteViewController.swift (2)

3-3: BKCore import 추가 LGTM

ScreenLoggable/GATracking 의존성 충족을 위해 필요해 보입니다.


194-196: 완료 이벤트 로깅 타이밍 적절

다이얼로그 표시 직전에 complete 로그 남기는 흐름 합리적입니다(상태 스트림에서 중복 방지도 적용됨).

presentRegistrationSuccessDialog가 재진입되지 않도록 상위 상태 흐름의 removeDuplicates 조건이 충분한지 한번만 확인해주세요.

src/Projects/Booket/Sources/AppDelegate.swift (1)

3-3: BKCore 임포트 추가 OK

GAManager 사용을 위한 임포트 추가는 적절합니다.

src/Projects/BKCore/Sources/Tracking/GAManager.swift (1)

60-71: 검증 완료 — iOS 16 API 사용 안전 (추가 분기 불필요)

프로젝트 최소 배포 타깃이 iOS 16.0 이상으로 확인되어 Locale.language.languageCode 사용은 안전합니다 (검증 근거: 저장된 프로젝트 정보).

Comment on lines +8 to +15
public static func configureAnalytics() {
#if DEBUG
Analytics.setAnalyticsCollectionEnabled(false)
Analytics.setUserProperty("true", forName: "debug_mode")
#else
Analytics.setAnalyticsCollectionEnabled(true)
Analytics.setUserProperty("false", forName: "debug_mode")
#endif
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

릴리스 수집 무조건 활성화는 동의(Consent) 요건 위반 소지·디버그 비활성화는 PR 설명과 충돌

  • 릴리스에서 setAnalyticsCollectionEnabled(true)를 즉시 호출하면 EEA/UK 등 동의 없이는 수집 금지 요건을 위반할 수 있습니다. 동의 후에만 활성화하도록 게이트하세요.
  • 디버그에서 수집을 꺼두면(현재 코드) 시뮬레이터에서 태그가 안 찍힌다는 PR 설명과 동일한 문제가 지속됩니다. 디버그 빌드에서는 수집을 켜고 DebugView로 필터링하는 편이 개발 효율적입니다.
  • 또한 디버그 분기에서 debug_mode 사용자 속성을 설정하더라도 수집이 꺼져 있으면 전송되지 않습니다.

권장 수정:

  • 디버그: 수집 ON(또는 DebugView 플래그 사용), 릴리스: 기본 비활성화 후 동의 시점에만 ON.

제안 패치:

 public static func configureAnalytics() {
-        #if DEBUG
-            Analytics.setAnalyticsCollectionEnabled(false)
-            Analytics.setUserProperty("true", forName: "debug_mode")
-        #else
-            Analytics.setAnalyticsCollectionEnabled(true)
-            Analytics.setUserProperty("false", forName: "debug_mode")
-        #endif
+        #if DEBUG
+            // 개발 편의를 위해 디버그 빌드는 수집 ON + 디버그 표시
+            Analytics.setAnalyticsCollectionEnabled(true)
+            Analytics.setUserProperty("true", forName: "debug_build")
+        #else
+            // 릴리스는 동의 이전엔 비활성 상태 유지 (동의 후 별도 API에서 활성화)
+            Analytics.setAnalyticsCollectionEnabled(false)
+            Analytics.setUserProperty("false", forName: "debug_build")
+        #endif

동의 적용용 API(동일 파일에 추가)를 별도로 제공 바랍니다:

public static func applyConsent(analyticsGranted: Bool) {
    if analyticsGranted {
        Analytics.setConsent([.analyticsStorage: .granted])
        Analytics.setAnalyticsCollectionEnabled(true)
    } else {
        Analytics.setConsent([.analyticsStorage: .denied])
        Analytics.setAnalyticsCollectionEnabled(false)
    }
}
🤖 Prompt for AI Agents
In src/Projects/BKCore/Sources/Tracking/GAManager.swift around lines 8-15, the
current code forcibly enables analytics collection in release builds and
disables it in debug builds which breaks consent requirements and the PR intent;
change the debug branch to enable collection (so DebugView works) and set
debug_mode, change the release branch to leave collection disabled by default
(do NOT call setAnalyticsCollectionEnabled(true) at startup), and add a public
applyConsent(analyticsGranted: Bool) API in the same file that sets
Analytics.setConsent([.analyticsStorage: .granted/.denied]) and toggles
Analytics.setAnalyticsCollectionEnabled(true/false) based on the consent flag so
analytics are enabled only after explicit consent.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/Projects/BKCore/Sources/Tracking/GAManager.swift (1)

9-15: 수집 토글 위치 재설계: 디버그=ON, 릴리스=동의 후 ON

현재 디버그에서 수집 OFF, 릴리스에서 즉시 ON은 (1) PR 설명의 “시뮬레이터 수집 안 됨”과 충돌, (2) EEA/UK 등 동의 요건 위반 소지. 디버그는 개발 편의상 ON, 릴리스는 기본 OFF 후 동의 시점에만 ON으로 전환하세요.

아래 패치를 적용해 기본 정책을 바꿔주세요:

-        #if DEBUG
-            Analytics.setAnalyticsCollectionEnabled(false)
-            Analytics.setUserProperty("true", forName: "debug_mode")
-        #else
-            Analytics.setAnalyticsCollectionEnabled(true)
-            Analytics.setUserProperty("false", forName: "debug_mode")
-        #endif
+        #if DEBUG
+            // 디버그: DebugView 확인을 위해 수집 ON
+            Analytics.setAnalyticsCollectionEnabled(true)
+            Analytics.setUserProperty("true", forName: "debug_build")
+        #else
+            // 릴리스: 동의 전까지 비활성화
+            Analytics.setAnalyticsCollectionEnabled(false)
+            Analytics.setUserProperty("false", forName: "debug_build")
+        #endif

동의 반영용 API를 동일 파일에 추가해 주세요(앱의 동의 플로우에서 호출):

public static func applyConsent(analyticsGranted: Bool) {
    if analyticsGranted {
        Analytics.setConsent([.analyticsStorage: .granted])
        Analytics.setAnalyticsCollectionEnabled(true)
    } else {
        Analytics.setConsent([.analyticsStorage: .denied])
        Analytics.setAnalyticsCollectionEnabled(false)
    }
}
🧹 Nitpick comments (2)
src/Projects/BKCore/Sources/Tracking/GAManager.swift (2)

30-35: 초기 동의 기본값에 analyticsStorage 포함

현재 adStorage/adUserData/adPersonalization만 거부 설정이며 analyticsStorage는 누락되어 있습니다. 릴리스 기본 OFF 정책과 일치하도록 초기값에 명시적으로 포함하세요.

-        Analytics.setConsent([
-          .adStorage: .denied,
-          .adUserData: .denied,
-          .adPersonalization: .denied
-        ])
+        Analytics.setConsent([
+          .analyticsStorage: .denied,
+          .adStorage: .denied,
+          .adUserData: .denied,
+          .adPersonalization: .denied
+        ])

3-5: 의존성 범위 점검: UIKit 의존 최소화 필요 여부 확인

BKCore 모듈에서 UIKit 사용이 꼭 필요한지 확인해 주세요. 화면 크기만 필요하다면 의존 역전(예: 호출부에서 파라미터 주입)도 고려할 수 있습니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e6b929 and dd13dd3.

📒 Files selected for processing (1)
  • src/Projects/BKCore/Sources/Tracking/GAManager.swift (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: doyeonk429
PR: YAPP-Github/Reed-iOS#157
File: src/Projects/BKPresentation/Sources/MainFlow/Home/View/HomeViewController.swift:19-24
Timestamp: 2025-08-08T01:38:59.656Z
Learning: doyeonk429는 Reed-iOS 프로젝트에서 접근성(accessibility) 관련 개선사항은 현재 작업 중인 PR에서 즉시 처리하지 않고, 접근성 전용 PR이나 이슈를 별도로 만들어 한번에 처리하는 것을 선호한다.
📚 Learning: 2025-08-19T10:38:27.309Z
Learnt from: clxxrlove
PR: YAPP-Github/Reed-iOS#178
File: src/Projects/BKPresentation/Sources/MainFlow/Note/ViewModel/OCRScannerViewModel.swift:98-98
Timestamp: 2025-08-19T10:38:27.309Z
Learning: In the Reed-iOS project, the preferred approach for handling debug logging that might contain PII is to add #if DEBUG guards inside the Log.debug implementation itself (in BKCore module) rather than at individual call sites, since debug logs are not needed in release builds and production telemetry should be handled through Firebase Crashlytics instead.

Applied to files:

  • src/Projects/BKCore/Sources/Tracking/GAManager.swift
🔇 Additional comments (1)
src/Projects/BKCore/Sources/Tracking/GAManager.swift (1)

8-41: 확인: Firebase 초기화 순서·디버그 스킴 설정 정상
FirebaseApp.configure()가 GAManager.configureAnalytics()보다 먼저 호출됩니다 (src/Projects/Booket/Sources/AppDelegate.swift). 디버그 런치 아규먼트 -FIRAnalyticsDebugEnabled가 설정되어 있습니다 (src/Projects/Booket/Project.swift:108).

Comment on lines +53 to +80
private static func setLocationInfo() {
let currentLocale = Locale.current
var params: [String: Any] = [:]

// 국가 코드
if let countryCode = currentLocale.region?.identifier {
params["country_code"] = countryCode
}

// 언어 코드
if let languageCode = currentLocale.language.languageCode?.identifier {
params["language_code"] = languageCode
}

// 전체 로케일
params["locale"] = currentLocale.identifier

// 국가 이름
if let countryCode = currentLocale.region?.identifier,
let countryName = currentLocale.localizedString(forRegionCode: countryCode) {
params["country_name"] = countryName
}

// 타임존
params["time_zone"] = TimeZone.current.identifier

Analytics.setDefaultEventParameters(params)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

로케일/타임존 등은 자동 수집·고카디널리티 우려 → 최소화

locale/time_zone/country_name은 자동 수집되거나 카디널리티가 큽니다. 지역 세분화가 필요하면 country_code 정도만 유지하세요.

     private static func setLocationInfo() {
         let currentLocale = Locale.current
-        var params: [String: Any] = [:]
+        var params: [String: Any] = [:]

         // 국가 코드
         if let countryCode = currentLocale.region?.identifier {
             params["country_code"] = countryCode
         }
 
         // 언어 코드
-        if let languageCode = currentLocale.language.languageCode?.identifier {
-            params["language_code"] = languageCode
-        }
-
-        // 전체 로케일
-        params["locale"] = currentLocale.identifier
-
-        // 국가 이름
-        if let countryCode = currentLocale.region?.identifier,
-           let countryName = currentLocale.localizedString(forRegionCode: countryCode) {
-            params["country_name"] = countryName
-        }
-
-        // 타임존
-        params["time_zone"] = TimeZone.current.identifier
+        if let languageCode = currentLocale.language.languageCode?.identifier {
+            params["language_code"] = languageCode
+        }
 
         Analytics.setDefaultEventParameters(params)
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static func setLocationInfo() {
let currentLocale = Locale.current
var params: [String: Any] = [:]
// 국가 코드
if let countryCode = currentLocale.region?.identifier {
params["country_code"] = countryCode
}
// 언어 코드
if let languageCode = currentLocale.language.languageCode?.identifier {
params["language_code"] = languageCode
}
// 전체 로케일
params["locale"] = currentLocale.identifier
// 국가 이름
if let countryCode = currentLocale.region?.identifier,
let countryName = currentLocale.localizedString(forRegionCode: countryCode) {
params["country_name"] = countryName
}
// 타임존
params["time_zone"] = TimeZone.current.identifier
Analytics.setDefaultEventParameters(params)
}
private static func setLocationInfo() {
let currentLocale = Locale.current
var params: [String: Any] = [:]
// 국가 코드
if let countryCode = currentLocale.region?.identifier {
params["country_code"] = countryCode
}
// 언어 코드
if let languageCode = currentLocale.language.languageCode?.identifier {
params["language_code"] = languageCode
}
Analytics.setDefaultEventParameters(params)
}
🤖 Prompt for AI Agents
In src/Projects/BKCore/Sources/Tracking/GAManager.swift around lines 53 to 80,
reduce high-cardinality and redundant auto-collected fields: remove adding
"locale", "language_code", "country_name", and "time_zone" to params and only
keep the minimal "country_code" (country_code is sufficient for regional
segmentation); update params population to only set country_code when available
and call Analytics.setDefaultEventParameters with that single-field dictionary.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
src/Projects/BKCore/Sources/Tracking/GAManager.swift (3)

9-15: 릴리스 기본 수집 ON/디버그 OFF → 동의(Consent) 요건·PR 설명과 충돌. 릴리스는 동의 전 비활성, 디버그는 활성로 전환하세요.

  • 릴리스에서 즉시 수집 ON은 EEA/UK 등에서 동의 이전 수집 리스크가 있습니다. 초기값은 비활성 + analyticsStorage=denied로 두고, 동의 시점에만 활성화하세요.
  • 디버그에서 수집 OFF라 시뮬레이터 태그 미수집 이슈가 계속됩니다. 디버그는 개발 편의상 수집 ON이 적절합니다.

적용 diff(토글 수정):

-        #if DEBUG
-            Analytics.setAnalyticsCollectionEnabled(false)
-            Analytics.setDefaultEventParameters(["debug_mode": "true"])
-        #else
-            Analytics.setAnalyticsCollectionEnabled(true)
-            Analytics.setDefaultEventParameters(["debug_mode": "false"])
-        #endif
+        #if DEBUG
+            // 디버그: DebugView 확인을 위해 수집 ON
+            Analytics.setAnalyticsCollectionEnabled(true)
+            Analytics.setDefaultEventParameters(["debug_mode": "true"])
+        #else
+            // 릴리스: 동의 전까지 수집 OFF
+            Analytics.setAnalyticsCollectionEnabled(false)
+            Analytics.setDefaultEventParameters(["debug_mode": "false"])
+        #endif

초기 Consent 기본값 보강(analyticsStorage 포함):

-        Analytics.setConsent([
-          .adStorage: .denied,
-          .adUserData: .denied,
-          .adPersonalization: .denied
-        ])
+        Analytics.setConsent([
+          .analyticsStorage: .denied,
+          .adStorage: .denied,
+          .adUserData: .denied,
+          .adPersonalization: .denied
+        ])

동의 적용 API 추가(파일 내 임의 위치, 예: 클래스 하단):

public static func applyConsent(analyticsGranted: Bool) {
    Analytics.setConsent([.analyticsStorage: analyticsGranted ? .granted : .denied])
    Analytics.setAnalyticsCollectionEnabled(analyticsGranted)
}

동의 플로우(예: Onboarding/설정 화면)에서 사용자가 동의하면 GAManager.applyConsent(true)를 호출해 주세요. 원하시면 연결 위치까지 패치 드리겠습니다.

Also applies to: 22-26


35-41: device_identifier 수집은 지문화(fingerprinting)·정책 리스크. 제거 권장(유지 시 최소한 프로드 비활성).

  • GA4가 디바이스/OS 속성을 자동 수집합니다. machine ID(iPhone15,2 등)까지 기본 파라미터로 상시 전송하면 정책·카디널리티 리스크가 큽니다. 화면 해상도 정도만 유지하세요.

적용 diff(기기 파라미터 최소화):

     private static func setDeviceInfo() {
-        Analytics.setDefaultEventParameters([
-            "device_identifier": getDeviceIdentifier(),
-            "screen_size": "\(Int(UIScreen.main.bounds.width))x\(Int(UIScreen.main.bounds.height))",
-            "screen_scale": "\(UIScreen.main.scale)"
-        ])
+        Analytics.setDefaultEventParameters([
+            "screen_size": "\(Int(UIScreen.main.bounds.width))x\(Int(UIScreen.main.bounds.height))"
+        ])
     }

식별자 유틸 제거:

-    private static func getDeviceIdentifier() -> String {
-        var systemInfo = utsname()
-        uname(&systemInfo)
-        let machineMirror = Mirror(reflecting: systemInfo.machine)
-        let identifier = machineMirror.children.reduce("") { identifier, element in
-            guard let value = element.value as? Int8, value != 0 else { return identifier }
-            return identifier + String(UnicodeScalar(UInt8(value)))
-        }
-        return identifier
-    }

Also applies to: 72-81


43-70: 로케일/국가명/타임존/언어코드 기본 파라미터는 고카디널리티·자동수집 중복. country_code만 유지 권장.

  • locale, country_name, time_zone, language_code는 중복·변동성이 커 리포트 품질을 해칩니다. 지역 세분화가 필요하면 country_code만으로 충분한 경우가 많습니다.

적용 diff(최소화):

-    private static func setLocationInfo() {
-        let currentLocale = Locale.current
-        var params: [String: Any] = [:]
-
-        // 국가 코드
-        if let countryCode = currentLocale.region?.identifier {
-            params["country_code"] = countryCode
-        }
-
-        // 언어 코드
-        if let languageCode = currentLocale.language.languageCode?.identifier {
-            params["language_code"] = languageCode
-        }
-
-        // 전체 로케일
-        params["locale"] = currentLocale.identifier
-
-        // 국가 이름
-        if let countryCode = currentLocale.region?.identifier,
-           let countryName = currentLocale.localizedString(forRegionCode: countryCode) {
-            params["country_name"] = countryName
-        }
-
-        // 타임존
-        params["time_zone"] = TimeZone.current.identifier
-
-        Analytics.setDefaultEventParameters(params)
-    }
+    private static func setLocationInfo() {
+        if let countryCode = Locale.current.region?.identifier {
+            Analytics.setDefaultEventParameters(["country_code": countryCode])
+        }
+    }
🧹 Nitpick comments (1)
src/Projects/BKCore/Sources/Tracking/GAManager.swift (1)

28-33: setDefaultEventParameters 다중 호출 → 한 번에 합치면 초기화가 명확합니다.

  • 동작상 병합되지만, 한 번에 세팅하면 키 충돌·순서 의존을 줄일 수 있습니다.

예시:

public static func configureAnalytics() {
    // ...토글/동의 설정...

    var defaults: [String: Any] = [
        "debug_mode": isDebug ? "true" : "false",
        "screen_size": "\(Int(UIScreen.main.bounds.width))x\(Int(UIScreen.main.bounds.height))"
    ]
    #if DEBUG && targetEnvironment(simulator)
    defaults["simulator"] = "true"
    #endif
    if let country = Locale.current.region?.identifier {
        defaults["country_code"] = country
    }
    Analytics.setDefaultEventParameters(defaults)
}

Also applies to: 35-41, 43-70

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd13dd3 and e298efd.

📒 Files selected for processing (1)
  • src/Projects/BKCore/Sources/Tracking/GAManager.swift (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: doyeonk429
PR: YAPP-Github/Reed-iOS#157
File: src/Projects/BKPresentation/Sources/MainFlow/Home/View/HomeViewController.swift:19-24
Timestamp: 2025-08-08T01:38:59.656Z
Learning: doyeonk429는 Reed-iOS 프로젝트에서 접근성(accessibility) 관련 개선사항은 현재 작업 중인 PR에서 즉시 처리하지 않고, 접근성 전용 PR이나 이슈를 별도로 만들어 한번에 처리하는 것을 선호한다.
📚 Learning: 2025-08-19T10:38:27.309Z
Learnt from: clxxrlove
PR: YAPP-Github/Reed-iOS#178
File: src/Projects/BKPresentation/Sources/MainFlow/Note/ViewModel/OCRScannerViewModel.swift:98-98
Timestamp: 2025-08-19T10:38:27.309Z
Learning: In the Reed-iOS project, the preferred approach for handling debug logging that might contain PII is to add #if DEBUG guards inside the Log.debug implementation itself (in BKCore module) rather than at individual call sites, since debug logs are not needed in release builds and production telemetry should be handled through Firebase Crashlytics instead.

Applied to files:

  • src/Projects/BKCore/Sources/Tracking/GAManager.swift
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
src/Projects/BKCore/Sources/Tracking/GAManager.swift (2)

17-20: 시뮬레이터 플래그를 기본 이벤트 파라미터로만 표기한 점 좋습니다.
디버그 한정·비영속 파라미터 사용은 적절합니다.


52-55: 승인 — Locale.current.language 사용 안전

장기 메모 확인 결과 Reed-iOS 프로젝트의 최소 iOS 배포 타깃이 iOS 16.0 이상이므로 Locale.current.language.languageCode 사용은 컴파일 이슈가 없으며 변경 불필요합니다.

@clxxrlove clxxrlove merged commit 74e5f3a into develop Sep 15, 2025
3 checks passed
@clxxrlove clxxrlove deleted the BOOK-267-feature/#174 branch September 15, 2025 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BOOK-267/feat] GA 세팅 및 로깅 이벤트 추가

2 participants