iOS14でのIDFA取得

iOS14からユーザトラッキングなどに用いられているIDFA取得のオプトアウトの選択がアプリごとに強制されます。この記事ではiOS14でどのように挙動が変わるかを記載しています。

（2020/6/22公開のXcode12beta/iOS14betaによる調査です。）
（追記：Xcode11とiOS13.4.1でインストールした後、iOS14betaにアップデートしても同じ挙動でした。）
（追記：2020/7/22公開のbeta3でも同じ挙動でした。）
（追記：2020/9/3にこの施策が2021年はじめに延期されることが発表されました。）
（追記：2020/9/3に公開のbeta7での挙動を確認し、対応方法を追記しました。）
（追記：2020/9/9に公開のbeta8でもbeta7と同じ挙動でした。）
（追記：2020/9/15に公開のGMでもbeta7と同じ挙動でした。繰り返しですが、ダイアログ表示必須化は延期されましたが、IDFA取得許可関連APIの変更は延期されていません。）


# ダイジェスト

- iOS14ではトラッキングID取得前のユーザ確認ダイアログの実装が必須化されます
- **[重要]** 対応していないアプリでも挙動が変わり、IDFAが取得できなくなります
- **[重要]** 2021年に延期されましたが、挙動は変わっており、たいていの場合IDFAが取得できなくなります(iOS14beta7)

# IDFAとは

IDFAはIdentifier For Advertisingの略で、広告目的で用いられることを意図したiOSの端末固有のIDです。固有IDを用いることによって広告プラットフォームがユーザを特定し、ターゲット広告を表示できます。一般的にはトラッキングIDと呼ばれます。Androidでは同等のものとしてADID (Google Play Services ID for Android) が提供されています。 
トラッキングIDはターゲット広告の他に、アプリ広告の効果計測や、再インストール（リセマラ）の検出などにも用いられています。

もともとトラッキングIDとしては、MACアドレスやスマートフォンのデバイスID（IMEI等)、UDIDなどのありとあらゆる識別子をデベロッパーが好き勝手に使っていましたが、AppleとGoogleはスマートフォンの広告プラットフォームが第三者に奪われることを嫌って、個人情報保護を名目にiOS/Androidのシステムが提供することになりました。

# 従来のIDFAのオプトアウト

iOS5でIDFAが導入されると同時にトラッキングIDをユーザがオプトアウト（選択的に除外)できる手段が提供されました。選択はiOSの設定アプリの以下ので順で行います。

> 設定＞プライバシー＞広告（旧称：アドバタイズ）＞追跡型広告を制限（旧称：Ad Trackingを制限)

デフォルトはOFF（制限しない）になっています。設定の奥の方にあるのでたいていのユーザは気づかずにデフォルトのままになっていると思われます。

実際のIDFAにアクセスするためには **AdSupport.framework** を追加する必要があります。コードを下に示します。

```Objective-c
    ASIdentifierManager *identifierManager = [ASIdentifierManager sharedManager];
    if ([identifierManager isAdvertisingTrackingEnabled]) {
        NSLog(@"[idfa] isAdvertisingTrackingEnabled enabled");
        NSString *idfa = identifierManager.advertisingIdentifier.UUIDString;
        NSLog(@"[idfa] %@", idfa);
    } else {
        NSLog(@"[idfa] isAdvertisingTrackingEnabled disabled");
    }
```

**isAdvertisingTrackingEnabled** が、上の設定アプリでのオプトアウトと連動しています。

参考：[ASIdentifierManager](https://developer.apple.com/documentation/adsupport/asidentifiermanager)


# iOS14でのIDFA

WWDC2020で、iOS14でトラッキングIDの扱いに変更が加えられることが発表されました。
iOS14以降はIDFAを取得する前にトラッキングID取得の可否をユーザに選択させるダイアログを表示することが義務付けられます。

https://developer.apple.com/app-store/user-privacy-and-data-use/

実際には、iOS14で新規追加された **AppTrackingTransparency** フレームワークを用いて、（おそらくはアプリ起動の最初期に）ダイアログを表示します。ユーザに許可を求めるダイアログの文言はInfo.plistに記述します。Info.plistに記述するということは後から変更することができず、かつ、Appleのレビュー対象となります。推測ですが、同意しないと進めない等の強制的な文言はレビューでNGになると思われます。
Info.plistの文言は多言語化することができますが、ひと手間かかりますので対応が必要です。

参考：[AppTrackingTransparency](https://developer.apple.com/documentation/apptrackingtransparency)

<img width="412" alt="AdTest_dialogpng.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/559126/2541eca3-9364-773c-39dc-70c2170a347c.png">

多くのユーザにとっては即座に理解できない許可を求められても許諾するとは思われないので、IDFA（トラッキングID）の取得は今後困難になると思われます。

日本においては地上波テレビの影響力が大きく、アプリのインストールはテレビCMなど広告経由によるものが多いです。そのためアプリ開発会社は広告会社（電通など）に多額の広告費を支払います。支払う金額の根拠としてトラッキングIDなどを用いて広告費の対費用効果（コスパ）を計測するのですが、トラッキングができなくなると、広告会社（電通など）になんとなくジャブジャブお金を支払っていた昭和の時代に逆戻りする可能性がありますね。
Googleが収益の70%を広告から得ている一方で、AppleはiAd大失敗しました。そうしたことも影響しているのかもしれません。今回Appleは新しいSKAdNetworkに誘導しようとしています。味わい深いですね。

参考：[SKAdNetwork](https://developer.apple.com/documentation/storekit/skadnetwork)


閑話休題

# iOS14betaでの実際の挙動

Xcode12betaと、iOS13.4.1実機／iOS14.0beta実機の組み合わせ確認しました。
グローバル設定の「追跡型広告を制限」はOFF（追跡可能）になっています。

### iOS Deployment Target = 13.4の場合


| iOS実機 | isAdvertisingTrackingEnabled | advertisingIdentifier.UUIDString |
|:-:|:-:|:-:|
| iOS13.4.1 | true | 実際のIDFA |
| iOS14.0beta | false | 00000000-0000-0000-0000-000000000000 |

### iOS Deployment Target = 14.0の場合

| iOS実機 | ユーザ許諾 | isAdvertisingTrackingEnabled | advertisingIdentifier.UUIDString |
|:-:|:-:|:-:|:-:|
| ~~iOS13.4.1~~ | ~~N/A~~ | ~~false~~ | ~~00000000-0000-0000-0000-000000000000~~ |
| iOS14.0beta | NG | false | 00000000-0000-0000-0000-000000000000 |
| iOS14.0beta | OK | false | 実際のIDFA |

ユーザ許諾とは **requestTrackingAuthorizationWithCompletionHandler** を用いて、ユーザにトラッキングID取得の許諾を請うダイアログを表示してその選択結果をさします。
UUIDStringはユーザ許諾の結果の可否を問わずに取得を試みています。

## つまりどういうこと？

iOS14ではグローバル設定や許諾ダイアログの可否に関わらず、**常にisAdvertisingTrackingEnabledはfalse**です。つまり、従来コードもグローバル設定で「追跡型広告を制限」がONにされた場合のコードパスを常に通ることになります。
また、iOS Deployment Targetを13以前にした場合も同じ挙動です。isAdvertisingTrackingEnabledはiOS14でdeprecated指定されましたが、それと同時に挙動が変更になっており、異例の対応と言えます。

iOS14ではIDFAが取得できるのは、グローバル設定で「追跡型広告を制限」がOFF、かつ、許諾ダイアログの結果がOK、のときのみです。

## iOS14でのグローバル設定

設定アプリでのトラッキングID取得に関わるグローバル設定の方式が変わりました。

> 設定＞プライバシー＞Tracking＞Appからのトラッキングを許可

従来とは意味が逆になっています。
また、アプリごとにON/OFFができるようになりました。（おそらく許諾ダイアログを表示したことがあるアプリのみ）

## requestTrackingAuthorizationWithCompletionHandlerの挙動

- ATTrackingManagerのrequestTrackingAuthorizationWithCompletionHandlerは初回呼び出しでのみダイアログが表示され、ユーザ選択の結果はシステムに保存されます
- ２回目以降の呼び出しではユーザダイアログは表示されず、前回の回答結果が即座にcallbackに渡されます
- アプリをアンインストール／再インストールすることで再度ダイアログが表示されます
- グローバル設定の"Allow Apps to Request to Track"がOFFのときは、初回呼び出しでもダイアログは表示されず、ユーザ許諾NGの動作をします

# 検証コード

Xcode12betaで新しくシンプルなプロジェクトを作成して、ViewController.mに手を入れます。
実際に利用すためにはiOS Deployment TargetをiOS14にして、 **AdSupport.framework** と **AppTrackingTransparency.framework** を追加する必要があります。

```Objective-c:ViewController.m
#import "ViewController.h"
#import <AdSupport/ASIdentifierManager.h>
#import <AppTrackingTransparency/ATTrackingManager.h>

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // ユーザ許諾前に一度IDFAの取得を試みます
    ASIdentifierManager *identifierManager = [ASIdentifierManager sharedManager];
    if ([identifierManager isAdvertisingTrackingEnabled]) {
        NSLog(@"[idfa] isAdvertisingTrackingEnabled enabled");
    } else {
        NSLog(@"[idfa] isAdvertisingTrackingEnabled disabled");
    }
    NSString *idfa = identifierManager.advertisingIdentifier.UUIDString;
    NSLog(@"[idfa] %@", idfa);

    [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
        NSLog(@"in requestTrackingAuthorizationWithCompletionHandler");
        if (status == ATTrackingManagerAuthorizationStatusAuthorized) {
            NSLog(@"[requestTrackingAuthorization] authorized");
        } else if (status == ATTrackingManagerAuthorizationStatusDenied) {
            NSLog(@"[requestTrackingAuthorization] denied");
        } else {
            NSLog(@"[requestTrackingAuthorization] something else");
        }

        // ユーザ許諾後にIDFAの取得を試みます
        if ([identifierManager isAdvertisingTrackingEnabled]) {
            NSLog(@"[idfa] isAdvertisingTrackingEnabled enabled");
        } else {
            NSLog(@"[idfa] isAdvertisingTrackingEnabled disabled");
        }
        NSString *idfa = identifierManager.advertisingIdentifier.UUIDString;
        NSLog(@"[idfa] %@", idfa);
    }];
}

@end
```

# iOS14beta7での挙動変更

延期のお知らせ → [Details for app privacy questions now available
](https://developer.apple.com/news/?id=hx9s63c5)

2020/9/3にこの施策が2021年はじめに延期すると発表されたのを受けて、同日リリースされたiOS14.0beta7の挙動を実機で確認しました。

- 変更あり： ATTrackingManagerを使わなくても、advertisingIdentifier.UUIDStringでIDFAの実際の値が取得可能
- 変更なし： 常にisAdvertisingTrackingEnabledはfalse

と、中途半端に挙動が戻っていました。
公式ドキュメント [isAdvertisingTrackingEnabled](https://developer.apple.com/documentation/adsupport/asidentifiermanager/1614148-isadvertisingtrackingenabled) によると、iOS14.0でDeprecatedに変更はなく、常に **No** が返るとあります。

```Objective-c:iOS13時代の一般的なコード
    ASIdentifierManager *identifierManager = [ASIdentifierManager sharedManager];
    if ([identifierManager isAdvertisingTrackingEnabled]) {
        NSString *idfa = identifierManager.advertisingIdentifier.UUIDString;
        // 取得したIDFAで何かするコード
    } else {
        NSLog(@"failure");
        // IDFAが取得できなかった場合のコード
    }
```

iOS10以降で、IDFAを取得する場合は、上のようにisAdvertisingTrackingEnabledを先にチェックするのが推奨されている方法でしたから、**isAdvertisingTrackingEnabledが常にfalse** の変更が戻らないと既存コードではIDFAが取得できなかった場合のコードパスが使われてしまいます。

isAdvertisingTrackingEnabled をチェックせずに UUIDString を取得するという乱暴な変更をしてもよいかもしれませんが、その場合、ユーザがプライバシー設定で「トラックしない」を選択(オプトアウト)しても無視することになり、iOS13時代よりプライバシーレベルが下がることになります。

```Objective-c:iOS14(2021年はじめまで)の乱暴なIDFA取得方法
    ASIdentifierManager *identifierManager = [ASIdentifierManager sharedManager];
    NSString *idfa = identifierManager.advertisingIdentifier.UUIDString;
    // 取得したIDFAで何かするコード（ユーザのオプトアウト無視）
```

iOS13のときのようにプライバシー設定のトラッキングの項目を尊重する場合は、やはりiOS14で追加された **ATTrackingManager** の **trackingAuthorizationStatus** プロパティを参照する必要があります。

```Objective-c:iOS14(2021年はじめまで)の紳士的なIDFA取得方法
    if (@available(iOS 14, *)) {
        // iOS14用(ただし暫定)の処理
        ATTrackingManagerAuthorizationStatus status =
[ATTrackingManager trackingAuthorizationStatus];
        if (status == ATTrackingManagerAuthorizationStatusAuthorized
            || status == ATTrackingManagerAuthorizationStatusNotDetermined) {
            // ユーザがプライバシー設定で「トラックしない」を選択していない（オプトイン）もしくはrequestTrackingAuthorizationWithCompletionHandlerでダイアログをまだ表示していない
            NSString *idfa = identifierManager.advertisingIdentifier.UUIDString;
            // 取得したIDFAで何かするコード
        } else if (status == ATTrackingManagerAuthorizationStatusDenied) {
            // ユーザがプライバシー設定で「トラックしない」を選択（オプトアウト）
            NSLog(@"failure");
            // IDFAが取得できなかった場合のコード
        } else {
            // それ以外のなにか
            NSLog(@"failure");
        }
    } else {
        // iOS13以前の処理
        ASIdentifierManager *identifierManager = [ASIdentifierManager sharedManager];
        if ([identifierManager isAdvertisingTrackingEnabled]) {
            NSString *idfa = identifierManager.advertisingIdentifier.UUIDString;
            // 取得したIDFAで何かするコード
        } else {
            NSLog(@"failure");
            // IDFAが取得できなかった場合のコード
        }
    }
```
うんざりするほど、長いですね。

**requestTrackingAuthorizationWithCompletionHandler** をつかってiOS14用のダイアログを表示しなくても、 **trackingAuthorizationStatus** は値を返すことができます。その場合、返り値は **NotDetermined** です。
（beta7での挙動です。ここらへんのbeta6以前の挙動を調査するのを忘れていました。）

~~まだ今後のβ版で変わりそうな予感がします。Stay tuned!~~ GMまで変更されませんでした。