-
Notifications
You must be signed in to change notification settings - Fork 1
[Feat] Meta SDK 연동 및 광고 전환 이벤트 로깅 구현 #96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| // | ||
| // MetaAnalyticsLogger.swift | ||
| // App | ||
| // | ||
| // Created by 이동현 on 8/22/25. | ||
| // | ||
|
|
||
| import Domain | ||
| import FacebookCore | ||
| import Foundation | ||
|
|
||
| /// 도메인 이벤트를 Meta(Facebook) 광고 전환 이벤트로 변환해 전송합니다. | ||
| /// "첫 루틴 완료만 전송" 같은 트래킹 정책(중복 제거 포함)은 이 어댑터가 책임집니다. | ||
| final class MetaAnalyticsLogger: AnalyticsLoggerProtocol { | ||
|
|
||
| private enum StorageKey { | ||
| static let didLogFirstRoutineCompletion = "didLogFirstRoutineCompletion" | ||
| } | ||
|
|
||
| func log(_ event: AnalyticsEvent) { | ||
| switch event { | ||
| case .signUpCompleted: | ||
| AppEvents.shared.logEvent(.completedRegistration) | ||
|
|
||
| case .onboardingCompleted: | ||
| AppEvents.shared.logEvent(.completedTutorial) | ||
|
|
||
| case .routineCompleted: | ||
| // 광고 최적화 신호 왜곡을 막기 위해 첫 루틴 완료만 전환 이벤트로 전송합니다. | ||
| guard !UserDefaults.standard.bool(forKey: StorageKey.didLogFirstRoutineCompletion) else { return } | ||
| UserDefaults.standard.set(true, forKey: StorageKey.didLogFirstRoutineCompletion) | ||
| AppEvents.shared.logEvent(.achievedLevel) | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,9 @@ | |
| // Created by 최정인 on 6/15/25. | ||
| // | ||
|
|
||
| import AppTrackingTransparency | ||
| import Domain | ||
| import FacebookCore | ||
| import KakaoSDKAuth | ||
| import Presentation | ||
| import Shared | ||
|
|
@@ -38,7 +40,20 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { | |
|
|
||
| func sceneDidDisconnect(_ scene: UIScene) { } | ||
|
|
||
| func sceneDidBecomeActive(_ scene: UIScene) { } | ||
| func sceneDidBecomeActive(_ scene: UIScene) { | ||
| // 유저가 설정 앱에서 추적 허용 여부를 바꿨을 수 있으므로, 활성화 시마다 최신 상태를 Meta SDK에 반영합니다. | ||
| Settings.shared.isAdvertiserTrackingEnabled = (ATTrackingManager.trackingAuthorizationStatus == .authorized) | ||
| } | ||
|
|
||
| // 광고 어트리뷰션 정확도를 위해 앱 추적 투명성(ATT) 권한을 요청하고, 결과를 Meta SDK에 반영합니다. | ||
| // 스플래시 애니메이션 완료 시점은 앱이 확실히 active 상태이므로 다이얼로그 표시가 보장됩니다. | ||
| private func requestTrackingAuthorization() { | ||
| guard ATTrackingManager.trackingAuthorizationStatus == .notDetermined else { return } | ||
|
|
||
| ATTrackingManager.requestTrackingAuthorization { status in | ||
| Settings.shared.isAdvertiserTrackingEnabled = (status == .authorized) | ||
| } | ||
| } | ||
|
|
||
| func sceneWillResignActive(_ scene: UIScene) { } | ||
|
|
||
|
|
@@ -51,6 +66,8 @@ extension SceneDelegate: SplashViewDelegate { | |
| func splashView(_ sender: Presentation.SplashViewController, isCompletedAnimated: Bool) { | ||
| guard isCompletedAnimated else { return } | ||
|
|
||
| requestTrackingAuthorization() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 8 'isCompletedAnimated|splashView\(|sceneDidBecomeActive|requestTrackingAuthorization' Projects/App/Sources --glob '*.swift'Repository: YAPP-Github/Bitnagil-iOS Length of output: 3664 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- SceneDelegate.swift ---'
cat -n Projects/App/Sources/SceneDelegate.swift
printf '%s\n' '--- SplashView symbols and implementations ---'
rg -n -C 10 'protocol SplashViewDelegate|class SplashViewController|isCompletedAnimated|SplashViewDelegate' Projects/App/Sources --glob '*.swift'
printf '%s\n' '--- ATT request call sites ---'
rg -n -C 6 'requestTrackingAuthorization|ATTrackingManager' Projects/App --glob '*.swift'Repository: YAPP-Github/Bitnagil-iOS Length of output: 9034 🌐 Web query:
💡 Result: According to official Apple documentation, the requestTrackingAuthorization(completionHandler:) method requires the application to be in the UIApplicationStateActive state to successfully display the tracking authorization prompt [1]. If you call this method while the application is in an inactive state—such as during the early stages of the application(:didFinishLaunchingWithOptions:) lifecycle method—the system will not display the prompt [2][3][4]. To resolve this and ensure the prompt is displayed: 1. Use applicationDidBecomeActive: You should trigger the request within the applicationDidBecomeActive(:) method in your AppDelegate [2][5][6]. 2. Use Scene Lifecycle Notifications: If your app uses scenes, the UIApplicationDelegate methods may not trigger as expected [5]. In this case, you should listen for the UIApplication.didBecomeActiveNotification notification, which is posted regardless of whether your app uses scenes, and trigger the request when your app becomes active [5][6]. 3. Verify Requirements: Ensure that the NSUserTrackingUsageDescription key is included in your Info.plist file; failure to include this key will cause the app to crash when the prompt is triggered [7][8]. Apple explicitly notes that the authorization prompt will also fail to display if another permission request is already pending user confirmation or if the request is made through an app extension [1]. Top results: [7][2][5][3][1] Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- SplashView-related files and dependency declarations ---'
git ls-files | rg -i 'splash|presentation|package\.swift|project\.pbxproj|podfile|cartfile'
printf '%s\n' '--- All SplashView references ---'
rg -n -C 5 'SplashViewController|SplashViewDelegate|isCompletedAnimated' . --glob '!**/Build/**' --glob '!**/DerivedData/**'Repository: YAPP-Github/Bitnagil-iOS Length of output: 50380 ATT 요청을 활성 Scene에서 재시도하도록 수정하세요.
🤖 Prompt for AI AgentsSource: MCP tools |
||
|
|
||
| guard let userDataRepository = DIContainer.shared.resolve(type: UserDataRepositoryProtocol.self) | ||
| else { fatalError("userDataRepository 의존성이 등록되지 않았습니다.") } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| // | ||
| // AnalyticsEvent.swift | ||
| // Domain | ||
| // | ||
| // Created by 이동현 on 8/22/25. | ||
| // | ||
|
|
||
| /// 광고 전환 측정 등 분석 도구로 전달되는 도메인 이벤트입니다. | ||
| /// 도메인은 "어떤 일이 일어났는지"만 알리고, 전송 여부/횟수 등의 트래킹 정책은 구현체가 결정합니다. | ||
| public enum AnalyticsEvent { | ||
| /// 회원가입(약관 동의) 완료 | ||
| case signUpCompleted | ||
| /// 온보딩 완료 | ||
| case onboardingCompleted | ||
| /// 루틴 완료 | ||
| case routineCompleted | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| // | ||
| // AnalyticsLoggerProtocol.swift | ||
| // Domain | ||
| // | ||
| // Created by 이동현 on 8/22/25. | ||
| // | ||
|
|
||
| public protocol AnalyticsLoggerProtocol { | ||
| func log(_ event: AnalyticsEvent) | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: YAPP-Github/Bitnagil-iOS
Length of output: 50380
🏁 Script executed:
Repository: YAPP-Github/Bitnagil-iOS
Length of output: 23520
🏁 Script executed:
Repository: YAPP-Github/Bitnagil-iOS
Length of output: 43591
🏁 Script executed:
Repository: YAPP-Github/Bitnagil-iOS
Length of output: 691
첫 루틴 완료 이벤트 중복 전송을 방지하도록 원자적으로 갱신하세요.
두 UI delegate 경로가 별도의
Task를 생성하고 네트워크await후.routineCompleted를 기록합니다.UserDefaults조회와 저장을 lock 또는 actor로.achievedLevel전송까지 직렬화하세요.🤖 Prompt for AI Agents