iOS

App Tracking Transparency

GREEN.1229 2021. 10. 16. 10:51

안녕하세요. 그린입니다🟢

이번 포스팅에서는 App Tracking Transparency에 대해 학습해보겠습니다🧑🏻‍💻

 

우선 App Tracking Transparency가 무엇인지 알아보시죠!

App Tracking Transparency?

앱 추적 투명성이라고 해석할 수 있습니다.

iOS 14.5부터 애플에서 App Tracking Transparency 프레임워크가 추가되었어요.

기존에는 앱에서 사용자의 IDFA(IDentifier for Advertising)이라 불리는 광고 ID에 아무 제약없이 접근하여 수집할 수 있었어요.

그런데 아시다시피 요즘은 워낙 이런 개인정보추적에 민감하다보니 앱의 최초 실행 시 해당 앱이 추적하는것에 대한 허가를 사용자가 선택 할 수 있게되었습니다.

당연히 애플 정책에서는 앱에 필수로 들어가야하는 부분입니다.

 

IDFA?

광고 ID가 그럼 무엇일까요?

광고 ID는 사용자의 니즈를 파악하여 맞춤으로 서비스나 광고를 보여줄때 필요합니다.

대표적으로 페이스북이 이를 잘 활용했는데... App Tracking Transparency이 도입되면서 광고 매출이 떨어졌다는 소식이 들려요😱

 

추적권한 요청

이 추적권한에 대한 요청은 앱을 설치하고 딱 한번만 요청하여 설정할 수 있습니다.

만약 앱을 삭제하고 캐쉬들을 날리고 다시 들어가면 다시 당연히 뜨긴합니다☺️

딱 한번만 요청되기에 우리는 꼭 필요한 접근 지점에서 해당 추적권한 접근 요청의 얼럿을 띄울 수 있어야해요.

 

그렇다면 친애하는 애플이 만들어놓은 프레임워크를 한번 봐볼까요🧐

짜란, 저도 이번에 처음 봤는데 이렇게 프레임워크로 만들어두었죠!?

이 프레임워크를 대략적으로 공식문서를 통해 파보고 구현까지 간략히 해보도록 하겠습니다.

App Tracking Transparency

사용자 또는 장치를 추적하기 위해 앱 관련 데이터에 액세스하기 위해 사용자 권한을 요청하는 프레임워크

자 이 프레임워크를 사용하려면 아래와 같은 3가지 스텝이 필요합니다.

 

1. 사용자 기기에 설치된 앱에 대한 시스템 권한 경고 요청을 표시하도록 설정합니다 

 - 이 부분은 NSUserTrackingUsageDescription을 통해 설정 가능합니다. (아래에서 천천히 살펴볼거에요!)

2. 사용자에게 앱 추적에 관해 승인 요청을 위해 requestTrackingAuthorization(completionHandler:) 메서드를 호출합니다.

3. trackingAuthorizationStatus를 활용해 현재 사용자의 앱 추적권한에 대한 상태를 확인하고 그에 따라 처리해줍니다.

 

위 스텝으로 이뤄지는데 중요한 포인트로 다뤄야할것은 2가지입니다.

바로 NSUserTrackingUsageDescription와 승인요청과 상태를 보기위한 클래스인 ATTrackingManager 입니다.

그럼 세부적으로 보시죠!

 

NSUserTrackingUsageDescription

앱이 사용자 혹은 기기를 추적하기 위한 데이터 사용 권한을 요청하는 이유를 사용자에게 알리는 메시지입니다.

즉 얼럿이 나타날때 "그린 앱에서 사용자에게 맞춤 광고를 위해 추적 권한을 요청합니다" 요런 식의 메시지를 통해 사용자에게

'아, 이 앱이 어떤 목적으로 추적을 하려는구나' 라고 인식 시켜줄 수 있습니다.

해당 설정은 아래와 같이 Info.plist 파일에서 할 수 있습니다.

자 보이시죠!?

이렇게 Info.plist 파일에 다이얼로그가 나타낼 메시지를 담아주었습니다.

그 다음으론 앱에서 해당 요청을 띄워볼까요?

 

ATTrackingManager

class ATTrackingManager : NSObject

추적 승인 요청 및 앱의 추적 승인 상태를 제공하는 클래스입니다.

즉 두가지의 역할을 해줍니다.

승인 요청을 위한 얼럿을 제공하고 그에 따라 현재 추적 승인 상태를 제공해줍니다.

이 두가지를 가지고 만들어보죠ㅎㅎ

 

requestTrackingAuthorization(completionHandler:)

class func requestTrackingAuthorization(completionHandler completion: @escaping (ATTrackingManager.AuthorizationStatus) -> Void)

 

앱 관련 데이터에 액세스하기 위한 사용자 권한 승인 요청입니다.

두가지의 방식으로 호출할 수 있는데 위와 같이 컴플리션 핸들러로 사용해 동기적 흐름에서 호출하거나,

아래와 같이 해당 메서드를 비동기적으로 만들어 버려 호출 할 수 있습니다!

우리 그때 배웠던 async await가 여기 사용되네요☺️

class func requestTrackingAuthorization() async -> ATTrackingManager.AuthorizationStatus

해당 추적 요청은 위에서 말했듯 앱을 설치하고 딱 한번만 이뤄집니다.

그러기에 추적 상태를 매번 묻지 않기에 현재 추적 승인 상태를 파악하여 호출해주면 더 좋을것 같아요👍🏻

그럼 추적 승인 상태에는 어떤것들이 있고 어떻게 알아볼 수 있을까요?

 

trackingAuthorizationStatus

class var trackingAuthorizationStatus: ATTrackingManager.AuthorizationStatus { get }

해당 앱에 대한 최근의 권한 승인 상태입니다.

보다시피 set을 임의로 할 수 없습니다.

ATTrackingManager의 AuthorizationStatus의 상태를 가져옵니다.

 

ATTrackingManager.AuthorizationStatus

import Foundation

extension ATTrackingManager {

  @available(iOS 14, *)
  pulbic enum AuthorizationStatus : UInt {
    case notDetermined = 0
    case restricted = 1
    case denied = 2
    case authorizaed = 3
  }
  ...
}

소스코드를 까보면 위와 같이 앱 추적 승인 상태값으로 열거형으로 나타내고 UInt값으로 0부터 3까지의 값이 지정되어 있습니다.

1. notDetermined

 - 0의 값을 가지며 최초 실행되어 결정되지 않은 상태

2. restricted

 - 1의 값을 가지며 접근 권한이 말 그대로 제한되어 있는 상태

3. denied

 - 2의 값을 가지며 추적 접근 승인 요청이 거부된 상태

4. authorized

 - 3의 값을 가지며 승인이 되어 있는 상태

자 요렇게 요청을 할 수 있는 학습을 해보았어요.
그럼 요청을 해볼까요?
여기서 먼저 짚고 갈것이 iOS 15.0.1이전과 이후에서 차이가 난다는 것입니다.
우선 이전을 보여드릴께요!

iOS 15.0.1 이전

import UIKit
import AppTrackingTransparency

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    requestTrackingAuthorization()
    return true
  }
  
  ...
  
  // 추적 접근 요청 메서드
  private func requestTrackingAuthorization() {
    if #available(iOS 14, *) {
      if ATTrackingManager.trackingAuthorizationStatus == .notDetermined {
        ATTrackingManager.requestTrackingAuthorization(completionHandler: { _ in })
      }
    }
  }
  
}

자 보시면 이렇게 application이 didFinishLaunching되는 내장 메서드에서 호출해줍니다.

그럼 앱이 실행되고 런칭이 다되면 아래와 같이 짜잔하고 요청 얼럿이 떠요.

 

그쵸? 앱의 설정과 관련된 부분들은 여기서 해주는게 맞잖아요..? (저의 여태까지의 지식으론 그래요..)
그런데! iOS 15.0.1부터 이~상하게도 이렇게 동일하게 구현을 해주면 얼럿이 나타나지 않더라구요?
아마도 추측하건데 15로 넘어오면서 앱 설정 시 나타난 얼럿들은 호출은 되는데 닫혀버리게 되는것 같아요.
그에 대한 자세한 간증들... 
(https://www.reddit.com/r/iOSProgramming/comments/pt41jz/att_prompt_not_showing_on_ios_15/)

뭐.. 그래서 해결해야하잖아요..!?
우린 이것을 아래와 같이 해결해볼께요.

iOS 15.0.1 이후

import UIKit
import AppTrackingTransparency

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    return true
  }
  
  func applicationDidBecomeActive(_ application: UIApplication) {
    // 여기서 호출!!
    requestTrackingAuthorization()
  }
  
  ...
  
  // 추적 접근 요청 메서드
  private func requestTrackingAuthorization() {
    if #available(iOS 14, *) {
      if ATTrackingManager.trackingAuthorizationStatus == .notDetermined {
        ATTrackingManager.requestTrackingAuthorization(completionHandler: { _ in })
      }
    }
  }
  
}

applicationDidBecomeActive 메서드에서 호출을 해줌으로 해결할 수 있어요.

기존인 앱이 처음 실행될때가 아니라 앱이 Active 상태가 될때 호출해줘요.

음.. 저도 왜 기존처럼 하면 안되는지 아직 레퍼런스나 떠오르는걸 못찾았어요.

추측하시는게 있으면 알려주세요🧐

여하튼 이렇게 해줘야합니다.

앞으로는 꼭 이렇게 써야해요..!

왜냐면 안그러면... 심사 리젝을 당할거에요..

"추적 요청을 하는 부분을 못찾겠어요~~ 어쩌구 저쩌구.."

자..! 이렇게 구현해줘봤습니다.
끝내기전에 위는 UIKit의 앱 라이프 사이클 기준이고 iOS 15.0.1로 오면서 SwiftUI도 달라졌어요..
원래는 기존에는 최초 앱 실행되는 didFinish~~에서 해주면 만사 오케이였는데,
이후에 위와 같이 applicationDidBecomActive에서 호출해줘도 안됩니다.
이것도 아직 근거를 못찾았지만 SwiftUI에서의 뷰 라이프 사이클이 달라 이쪽에서 건드려주면 안되요..!
아마도 Xcode13, iOS15로 오면서 SwiftUI로 뷰를 구성할때 기존에는 라이프 사이클을 UIKit 방식과 SwiftUI 두가지 선택할 수 있었는데,
이제는 SwiftUI로 구성한다하면 SwiftUI의 라이프 사이클만 따르도록 아예 UIKit 선택 옵션을 빼버렸어요...ㅋㅋㅋㅋㅋㅋㅋㅋㅋ
그래서 그럴거 같습니다🥲

그래서 SwiftUI에선 어떻게 할까요?

자 함께 보시죠🙌

 

SwiftUI에서 ATT 얼럿 띄우기 (iOS 15.0.1 이후)

import AppTrackingTransparency
import SwiftUI

struct ContentView: View {
  var body: some View {
    VStack {
      Text("Hello, world!")
        .padding()
    }
    .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
      ATTrackingManager.requestTrackingAuthorization(completionHandler: { _ in
      })
    }
  }
}

이렇게 뷰단으로 가져와 노티피케이션으로 앱 상태를 onReceive받아 액션을 취해줌으로 해결할 수 있습니다.

요거에 대한 간증 레퍼런스는 아래!!

https://stackoverflow.com/questions/69283661/ios-15-how-to-display-att-dialog-when-the-app-starts-in-swiftui

 

iOS 15: How to display ATT dialog when the app starts in SwiftUI

In iOS 14, It could display ATT (App Tracking Transparency) dialog when app starts in SwiftUI as follows. func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [

stackoverflow.com

 

후.. 길었네요 정말..!!
이렇게 앱 추적 권한에 대해 학습해보았습니다!
다들 잘 사용해서 사용자의 정보를 안전하게 수집해보자구요????😁

 

[참고자료]

https://developer.apple.com/documentation/apptrackingtransparency

 

Apple Developer Documentation

 

developer.apple.com