1. 问题

神策 SDK 中会优先使用 IDFA 作为设备标识,不过引入 AdSupport 可能会提高 App 上架时的审核门槛,如何在不主动引入 AdSupport 库的情况下尝试获取 IDFA (p.s. 可能获取失败)?

即如何做到:

  1. App 中有获取 IDFA 权限,则获取 IDFA;
  2. App 中没有获取 IDFA 权限,则获取 IDFV。

2. 答案

2.1. 一、什么是 IDFA ?

IDFA 的英文全称是 Identifier For Advertising,是广告标识符的缩写,主要用于广告推广、换量等跨应用的用户追踪等。它也是一个由 32 位十六进制组成的序列,格式与 UUID 一致。在同一个特定的设备上,所有的应用程序获取到的 IDFA 都是相同的。

从 iOS 6 开始,我们可以利用 AdSupport.framework 提供的接口获取 IDFA。

#import <AdSupport/AdSupport.h>

NSString *idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];

但是,IDFA 的值也不是固定不变的,目前以下操作均为改变 IDFA 的值:

  • 通过设置 → 通用 → 还原 → 抹掉所有内容和设置,如下图。
  • 通过 iTunes 还原设备
  • 通过设置 → 隐私 → 广告 → 限制广告追踪,如下图。
  • 通过 iTunes 对备份进行还原

一旦用户限制了广告追踪,我们获取到的 IDFA 将是一个固定的 IDFA,即一连串的零:00000000-0000-0000-0000-000000000000。

所以,在获取 IDFA 之前,我们也可以利用 AdSupport.framework 提供的接口判断用户是否限制了广告追踪。

BOOL isLimitAdTracking = [[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled];
  • 通过设置 → 隐私 → 广告 → 还原广告标识符,如下图。

用户一旦还原了广告标识符,系统将会生成一个全新的 IDFA。

虽然 IDFA 有一些限制条件,但对于这些操作,只会在特定的情况下才会出现,而且还能解决卸载重装唯一标识设备的问题。所以,IDFA 目前来说比较适合作为设备 ID。

2.2. 二、引入 AdSupport 会产生什么影响 ?

不过开启 IDFA 也会带来一些副作用,需要在提交审核时对使用 IDFA 的目的进行说明,并且根据 App 类型不同,会相应提高审核的门槛。

App 开发者可以结合业务场景,综合判断是否需要使用 IDFA。不过 SDK 不能为了自己的目的而“强制”客户去使用 IDFA。对于 IDFA,SDK 需要有「得之我幸、失之我命」的觉悟。

新版本 Xcode 会自动链接项目中使用到的 Framework,所以如果 SDK 中直接 #import <AdSupport/AdSupport.h> 并显式调用 ASIdentifierManager 类的方法的话,会导致 App 在编译时自动引入 AdSupport.framework,App 在提交审核时就会被 Apple 判定使用了 IDFA。

2.3. 三、不主动引入 AdSupport 库,如何获取 IDFA ?

SDK 如果在不主动引入 AdSupport 的情况下获取 IDFA 呢,其中一个方式是在运行时尝试动态构造 ASIdentifierManager 类,这样并不会主动引入 AdSupport.framework,自然不会「改变」审核门槛。

如果构造成功,则代表 App 已经引入 AdSupport,SDK 就可以通过运行时调用方法的形式获取 IDFA。

如果构造失败,则代表 App 没有引入 AdSupport,SDK 需要使用其他标识,如 IDFV。

目前神策 SDK 中处理此逻辑的代码为:

+ (NSString *)getUniqueHardwareId {
    NSString *distinctId = NULL;
    Class ASIdentifierManagerClass = NSClassFromString(@"ASIdentifierManager");
    if (ASIdentifierManagerClass) {
        SEL sharedManagerSelector = NSSelectorFromString(@"sharedManager");
        id sharedManager = ((id (*)(id, SEL))[ASIdentifierManagerClass methodForSelector:sharedManagerSelector])(ASIdentifierManagerClass, sharedManagerSelector);
        SEL advertisingIdentifierSelector = NSSelectorFromString(@"advertisingIdentifier");
        NSUUID *uuid = ((NSUUID * (*)(id, SEL))[sharedManager methodForSelector:advertisingIdentifierSelector])(sharedManager, advertisingIdentifierSelector);
        distinctId = [uuid UUIDString];
        // 在 iOS 10.0 以后,当用户开启限制广告跟踪,advertisingIdentifier 的值将是全零
        // 00000000-0000-0000-0000-000000000000
        if (!distinctId || [distinctId hasPrefix:@"00000000"]) {
            distinctId = NULL;
        }
    }
    
    // 没有IDFA,则使用IDFV
    if (!distinctId && NSClassFromString(@"UIDevice")) {
        distinctId = [[UIDevice currentDevice].identifierForVendor UUIDString];
    }
    
    // 没有IDFV,则使用UUID
    if (!distinctId) {
        SADebug(@"%@ error getting device identifier: falling back to uuid", self);
        distinctId = [[NSUUID UUID] UUIDString];
    }
    return distinctId;
}
每日一问的答案中可能无法全完解读这个问题,如果您是相关技术专家或者是对本问题有自己的见解,欢迎带着「批判性」的态度阅读,指正其中的不足。