App Tracking Transparency using Combine.

May 04, 2021 · 5 mins read · Costantino Pistagna · @valv0

App Tracking Transparency using Combine.

One of the most publicized feature coming with iOS14.5 is the new App Tracking Trasparency feature. There are plenty of articles around that aim to pinpoint the relevant positions in this decision from Apple and counterparts like Facebook. I don’t want to digging out the political aspect of this feature. Instead, I would like to quickly overview what you need to work with this brand new capability and what this means in terms of technology underground.

App Tracking Trasparency consists of one piece of software in the form of a class named: ATTrackingManager.

class ATTrackingManager : NSObject

Apple states that this class provides all what you need to ask for permission and get the current status of your request. This class collaborates together with the Ad framework in order to identify anonymously users and let track their habits / interests.

shot1

Ad frameworks has a shared instance, available within the whole system, in the form of a class named ASIdentifierManager. We can ask for the shared instance as usual with the classical approach:

ASIdentifierManager.shared()

Furthemore, we can ask for the current anonymous identifier as easy as:

ASIdentifierManager.shared().advertisingIdentifier

Before iOS14.5 you (the developer) was always granted to get the above identifier in the form of an UUID object. This identifier is anonymous and consistent across all the operating system allowing to track users across different apps and domains. This, obviously, could lead to a lack of privacy if one or more players in this game are malicious (and, believe me, there’re tons of malicious players all the way around).

shot2

Since iOS14.5 you need to ask app based user consensus before you can access the above identifier. This means that you (the user) can decide who can access your identifier at every moment either when you install the app or - if you change your mind - from the Settings. That’s awesome in terms of user rights! And it’s not opinable at all.

shot3

Our goals for today is to wrap the above classes into a more convenient object that we could use both with Swift and SwiftUI. To achieve that we will use some basic Combine capabilities in order to expose correctly all pieces that we need.

For such of conciseness we will use a pure SwiftUI approach, but all the relevant code will be also ready for Swift standard projects as well. Let’s start and create a SwiftUI project.

The first thing to do is to add the relevant description within our Info.plist in order to populate correctly the message that will appear to our users. Pretty much as with CoreLocation or Photos, we need to describe with an effective sentence what and why we need user consensus to track. To do that, we need to add a new key NSUserTrackingUsageDescription and a paired phrase String as well.

shot4

Once we’re ready, we can start coding our helper class that will expose the relevant accessors to our App. We named this helper ATTrackingHelper. Since we want to be notified each time there’s a change either into AuthorizationStatus or AdvertisingIdentifier, we inherits from ObservableObject and mark the two relevant properties as @Published. This means that Combine, under the hood, will create for us all the relevant code in order to track and notify subscribers whenever there is a change.

shot5

The last missing piece in order to make this class useful for our purpose is to expose a requestAuth() method. The latter will interact with the framework and will populate the results accordingly.

class ATTrackingHelper: ObservableObject {
    @Published var status = ATTrackingManager.trackingAuthorizationStatus
    @Published var currentUUID = ASIdentifierManager.shared().advertisingIdentifier

    func requestAuth() {
        guard ATTrackingManager.trackingAuthorizationStatus != .authorized else {
            return
        }
        
        ATTrackingManager.requestTrackingAuthorization { status in
            DispatchQueue.main.async { [unowned self] in
                self.status = status
                if status == .authorized {
                    self.currentUUID = ASIdentifierManager.shared().advertisingIdentifier
                }
            }
        }
    }
}

Time for a Proof-Of-Concept. Given the above approach it will be as easy as declaring an ObservedObject within our ContentView. Pretty much like that:

struct ContentView: View {
    @ObservedObject var trackingHelper = ATTrackingHelper()

    var body: some View {
        VStack(alignment: .leading){
            Text("Your advertising ID:").font(.callout)
            Text("\(trackingHelper.currentUUID.uuidString)").font(.caption)
            HStack {
                Spacer()
                Button(action: {
                    trackingHelper.requestAuth()
                }, label: {
                    Text("Ask for tracking")
                })
                Spacer()
            }.padding(.vertical)
        }
        .padding()
    }
}

Now we can ask for user consensus, first time we will run our app and populate the required uuidString. What a great time to be an iOS developer! :-)



— Happy coding! 🖖🏻

Costantino Pistagna
Costantino Pistagna · @valv0 Costantino is a software architect, project manager and consultant with more than ten years of experience in the software industry. He developed and managed projects for universities, medium-sized companies, multi-national corporations, and startups. He is among the first teachers for the Apple's iOS Developer Academy, based in Europe. Regularly, he lectures iOS development around the world, giving students the skills to develop their own high quality apps. While not writing apps, Costantino improves his chefs skills, travelling the world with his beautiful family.