iOS中的设备唯一标识
在 iOS7 之前,曾经有过很多获取设备唯一标识的方法。但是它们都先后被苹果禁止掉了。这些被禁止掉的包括 UDID、Mac 地址、OpenUDID。在 iOS 7 之后,我们可以选择的唯一标识有 IDFA、IDFV、DeviceToken、UUID 四种方案。他们各有利弊,下面对他们进行对比。
IDFA
IDFA 是苹果 iOS 6 开始新增的广告标识符,用于给开发者跟踪广告效果用的,可以简单理解为 iPhone 的设备临时身份证,说是临时身份证是因为它允许用户更换。
IDFA 是目前苹果生态广告交易的主要标识,一般跟广告商交易一个用户后广告商需要给你提供用户的 IDFA 作为凭证,主流的广告平台腾讯广点通、新浪粉丝通对账是基于 IDFA 的。
IDFA(广告标识符)可以通过如下代码来获取:
// AdSupport.framework #import <AdSupport/AdSupport.h> // 获取IDFA NSString * IDFA = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
示例:E1D2194B-6C91-4094-8F5B-F7CAE5C50989
有如下几种情况下 IDFA 可能被重置。
- 重置系统(设置 -> 通用 -> 还原 -> 还原位置与隐私)
- 还原广告标识符(设置 -> 隐私 -> 广告 -> 还原广告标识符)
- iOS10 以下打开限制广告追踪(设置 -> 隐私 -> 广告 -> 限制广告追踪)
IDFA 在使用时需要注意如下几种情况:
- 在提交 App 的时候勾选对应的选项,不然可能遭到拒绝。
- 如果程序在后台运行,此时用户还原广告标识符,这时回到程序中并不会立即获得还原后的标识符。必须重新启动程序,才能获得还原后的广告标识符。
- 在 iOS10 以上打开限制广告追踪后,只能获取到 0。
IDFA 被淘汰
在 iOS 14 中,广告主或广告网盟将无法继续使用 IDFA,除非用户在每次下载新应用时同时向广告主 app 和广告网盟授予权限,允许其读取 IDFA。从技术角度来看,IDFA 并没有消失,只是我们预计用户选择授予权限的比率会非常低。我们基本上可以断言,IDFA 几乎失去了作用。
如果用户选择不接受 IDFA 跟踪,广告主就无法投放个性化和再营销广告,同时也无法准确对营销活动进行归因和了解营销活动的 ROI。
IDFV
IDFV是给Vendor标识用户用的,每个设备在所属同一个Vendor的应用里,都有相同的值。其中的Vendor是指应用提供商,准确的说,是通过BundleID的反转的前两部分进行匹配,如果相同就是同一个Vendor,例如对于 com.somecompany.appone,com.somecompany.apptwo 这两个BundleID来说,就属于同一个Vender,共享同一个idfv的值。和idfa不同的是,idfv的值是一定能取到的,所以非常适合于作为内部用户行为分析的主id,来标识用户,替代OpenUDID。
IDFV(Vendor 标识符)可以通过如下代码来获取:
NSString * IDFV = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
备注:
- idfv的值是一定能取到
- 同一台设备上,来自同一个供应商的 App,IDFV 相同。
- 同一台设备上,来自不同厂商的 App,IDFV 不同。
- 不同设备的 IDFV 不同,与供应商无关。换言之,即使同一个厂商的 App,在不同设备上 IDFV 也不相同。
- 当 iOS 设备上存在同一厂商的其他应用时,删除 App 重装,IDFV 保持不变。
- 删除 iOS 设备上同一厂商全部 App,重新安装 App,IDFV 会发生改变。
- 使用 Xcode 安装测试版本或 ad-hoc 包时,IDFV 会发生改变。
IDFV 只有在相同 Vendor 和相同设备里才具有唯一性。所以它不适合做不同 Vender 的 App 之前的唯一标识,但非常适合做相同 Vender 的 App 的唯一标识。不过,如果用户将属于此 Vender 的所有 App 卸载,IDFV 的值也会被重置。
iOS APNS DeviceToken
DeviceToken是APNs用于区分识别每个iOS设备和设备上不同app的一个标识符,还可以用于APNs通过它将推送消息路由到指定设备上。
- iOS6以前的系统,DeviceToken不随app变化:此时DeviceToken不含有bundle id信息,那是如何区分app的呢? 是因为apns和ios设备通信时通过ios设备上的证书可以区分app,当然这种方案也有bug:可能不同app之间的推送会乱。
- iOS7以后的系统,DeviceToken随app变化。此时apns推送到ios设备上的消息通过device token解析出来的bundle id即可投递给指定app。也就说是从信息量上看:device token = device id + bundle id。其中device id用于识别iOS设备, bundle id用于识别iOS设备上的app。
- 在0和iOS8.0的系统中,卸载重装应用,deviceToken不会发生变化,但是iOS9.0(包括)以后卸载重装应用,deviceToken会发生变化。
DeviceToken的一些特性:
- 开发环境获取的deviceToken和发布环境获取的deviceToken是不一样的
- 在一台设备中,deviceToken是系统级别的,不同App获得的deviceToken是相同的
- deviceToken会过期
- 单个App的更新deviceToken不会发生改变
- 当进行备份恢复、或恢复出厂设置之类的操作时,deviceToken会发生改变,建议App在每次启动时都获取deviceToken
- 用户抹除iPhone的数据时,为了保护隐私,deviceToken会改变
- 升级系统deviceToken有可能变化,猜测是升级大的系统版本后deviceToken会变化
- 删除手机上的App之后,再次下载安装,deviceToken在0+系统中会改变
DeviceToken 需要使应用拥有推送功能才能获取,所以单独为了唯一标识而开启推送是不值得的。
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{ NSMutableString *deviceTokenString = [NSMutableString string]; const char *bytes = (const char *)deviceToken.bytes; NSInteger count = deviceToken.length; for (int i = 0; i < count; i++) { [deviceTokenString appendFormat:@"%02x", bytes[i]&0x000000FF]; } NSLog(@"**发送给服务器的token字符串***:%@\n", deviceTokenString); }
SKAdNetwork
什么是 SKAdNetwork?
随着 iOS 14 的发布,Apple 推出了两个新的归因和广告监测框架。
- 第一个是 ATT (AppTrackingTransparency) 框架。在该框架下,要想访问用户的 IDFA,必须先取得用户的许可。
- 第二个是 Apple 在 2018 年推出的 SKAdNetwork。这是一种不同的推广活动监测方法,在 SKA 框架中,用户层级的数据不可用。随着 iOS 14 的推出,SKAdNetwork 框架将进行版本更新。Apple 这样做的目的是希望能减轻由于限制开发者的 IDFA 访问权限而造成的影响。
SKAdNetwork 的重要性何在?
SKAdNetwork 是接收 iOS 端营销推广活动归因数据的另一种方法。广告渠道必须向 Apple 注册,而开发者也要保证自己的应用与注册渠道及新框架兼容。但是,SKAdNetwork 会对移动营销人员当前的移动监测方式带来不少挑战。
- 按照当前的计划,SKAdNetwork 会提供长度为 6 比特的下游指标,且有 24 小时的时间限制。每次用户触发应用内事件,该 6 比特指标就会发生变化,成为应用指定的全新 6 比特事件识别码,而时间限制也会延长 24 小时。事件窗口过期后,第二个 24 小时归因窗口就会开始计时。SKAdNetwork 会在这 24 小时窗口内随机返回归因数据。SKAdNetwork 系统分享的数据都采取聚合形式,不提供用户层级上的精细数据。
- 事件识别码只能向上叠进。例如,用户在游戏内达成了等级 1,应用为 “等级 1” 创建的事件识别码为 000001;然后用户购买了游戏内货币,这一事件的识别码为 000011。如果用户随后达成了 “等级 2″,比特值不会变为 000010,因为变化是单向的。要避免这个问题,开发者需要为排列组合中所有的可能性分配不同的比特值,而不是为每种事件分配比特值。SKAdNetwork 跟踪的数据无法与 MMP SDK 跟踪的应用内事件精细数据关联起来。
- SKAdNetwork 框架下每个渠道仅显示 100 个不同的推广活动。乍一看上去,您或许觉得这并不是什么大问题。但这些推广活动之下,常常还有针对不同地区、设备类型或素材的无数子级推广活动。例如,如果您通过 SKAdNetwork 为五个不同的国家/地区使用十种不同的素材,那么在每个渠道就只能开展两个不同的推广活动。
SKAdNetwork 还会有 (最少) 24 至 48 小时的数据延迟,导致即时推广活动优化受阻,因此开发者和营销人员还要做出其他改变,才能妥善应对。此外,防广告欺诈领域也会发生变化。上文提到的事件转化值没有加密签名,也就是说,应用开发者无法使用现有的方法对事件进行验证,这给欺诈者留下了可乘之机。
当前,SKAdNetwork 不支持深度链接 (延迟和条件深度链接均不支持) 和展示归因,且除了下载以外,不会将其他任何活动视为可归因活动。要满足当今营销人员的需要,SKAdNetwork 当前的功能还需要升级,但对于想要使用 SKAdNetwork 的客户,我们也会提供全方位支持。
以下图表描述了安装验证的流程,App A 指展示广告的来源APP,App B 指用户安装的广告主APP。
请仔细看橘黄色的圆圈,这是这流程图的关键。SKAdNetwork是让广告平台在不获取IDFA的前提下对用户的点击和安装行为提供的一套追踪解决方案。这个层面的追踪已经跳开了设备这一个层面,直接内嵌在了广告平台和Apple Store之间,不再收集用户设备信息。
继续看橘黄色的圆圈,我们的目标是点击到安装都有一个身份证进行追踪是么?那么在SKAdNetwork的解决方案里面,Apple给每一次不同用户的点击行为注入了不同的标签(Ad Signature),这个标签一直伴随到安装结束。橘黄色的标签于广告平台(图二)生成(广告平台需要在Apple注册其平台)、用户点击行为(图三)以及用户安装行为(图四)。这样就撇开了设备层面的信息收集,从而变成了给每一次点击安装行为做了一个身份识别,从而完成追踪
你可以这么理解:
- SKAdNetwork的模式就好比你去电影院看电影,从买票(点击)到入场(安装),这张票(Ad Signature)上没有任何你的个人信息,售票处就好比广告平台,只知道这票在什么卖出去的,多少钱卖出去的,哪个渠道卖出去的,是否观影,什么时候观影。但是售票系统不知道买的人具体的特征,比如有没有买爆米花,年龄,性别,是否带女朋友还是每次换个女朋友看电影等信息。
- IDFA的模式就好比出境跟团游,你的护照、存款证明等(IDFA)被旅行社拿走,从而你的一切隐私可以被旅行社拿出来做各种促销,比如旅行社知道你是否消费了,消费了多少,年龄,性别,之前去过哪些国家等等,从而判断你属于什么类型的用户,进而推送更多的广告给你,无隐私可言。
SKAdNetwork的优势
我们还是看到了一些优势,比如AppStore会帮你验证安装的有效性- 这让那些欺诈的广告网络的作弊方式可以收敛一点。随着版本更新,未来我们应该会看到更多的优势。
SKAdNetwork现阶段的问题
在谈SKAdNetwork问题之前,我们先看下IDFA的优势:
- 可以在广告平台内做“用户召回(Re-Targeting)”,
- 可以追踪安装、注册、付费、留存等信息
但是SKAdNetwork的广告标签则没有这么细,只提供在点击到安装过程中的对应时间、对应来自于哪个广告组、具体哪个广告以及运营商网络信息。详
没有IDFA带来的问题还是蛮多的:比如最后一次点击和安装的匹配以及计算方式、网盟新的作弊方式的出炉、广告如何做针对留存和付费的优化,用户召回还怎么做等。
iCloud token
iCloud令牌,该属性的值是表示当前活动的iCloud帐户的唯一标记。您可以比较令牌以检测当前帐户是否与以前使用的帐户不同。如果用户在设备上启用飞行模式,则iCloud本身将无法访问,但当前的iCloud帐户仍保持登录状态。即使在飞行模式下,该ubiquityIdentityToken属性也包含当前iCloud帐户的令牌。如果用户退出iCloud,例如关闭“设置”中的“文档和数据”,则ubiquityIdentityToken属性的值将更改为nil。
NSFileManager* fileManager = [NSFileManager defaultManager]; id currentiCloudToken = fileManager.ubiquityIdentityToken;
奇巧淫技
仅对非越狱设备有效。不确定会一致有效。在系统不升级的情况下,ID唯一,与应用无关。
设备唯一标识使用到的技术
Keychain
Keychain Services 是 macOS 和 iOS 都提供一种安全的存储敏感信息的工具。比如,网络密码(用户访问服务器或者网站),通用密码(用来保存应用程序或者数据库密码)。与此同时,用于认证的证书、密钥、和身份信息也可以存储在Keychain中。Keychain Services 的安全机制保证了存储这些敏感信息不会被窃取。简单说来,Keychain 就是一个安全容器。在iOS中keychian 依赖用于签名的provisioning profile描述文件,确保发布不同版本的时候使用同一个文件。
苹果自己也用KeyChain来保存Wi-Fi密码,VPN凭证等,实际上以一个数据库,路径在/private/var/Keychains/keychain-2.db。
keyChain是一个相对独立的空间,当程序替换,删除时并不会删除keyChain的内容,这个要比Library/Cache好。刷机,恢复出厂应该就没有了。我们把keyChain看做一个Dictionary,所有数据都以Key-Value形式存储,可以对其进行add,update,get,delete四个操作,一个应用的keyChain都有两个访问区,私有区和公有区,私有区是一个个sandbox,本程序储存的对其他程序不可见,想将数据存在公有区,则需要声明公有区的名称,官方名称“keychain access group”
KeyChain的结构
每一个KeyChain由多个KeyChain item组成,KeyChain item的结构类似字典,为Key-Value,同时每条KeyChain Item还包含一条data和多个attributes组成。
比如一个银行就是一个KeyChain,银行里可以有多个保险库,对应的就是KeyChain Item,而每个保险库都有自己该存放的东西,比如现金,黄金等,这个就是attributes,而存储的内容就是data。
其中苹果提供了这些类型的keychain item,并且对不同类型的item做了不同的处理,比如password和key类的item就会做加密,而certificates类的就不会。
上述唯一标识的方案都存在或多或少有被重置或者获取不到的可能。但是他们都可以与 Keychina 进行结合。Keychina 是独立于软件外可以用来存放账号密码的区域,它不会随着软件的卸载和系统重置而消失。Keychina 的特性结合 UUID 的特性已经能做到近乎完美的设备唯一标识。这也是现在大部分软件的做法。
备注:一键改机工具可清除KeyChain数据。
FCUUID
FCUUID 是 iOS UUID / Universally Unique Identifiers 库,作为 UDID 和identifierForVendor 的替代品。
FCUUID该框架诞生于2015年10月左右,其实现原理就是CFUUID+KeyChain,跟已经废弃的OpenUDID是差不多的,只是存储方式不同,OpenUDID的原理是用UIPasteboard来保存UUID。需要注意的是,FCUUID框架依赖于框架UICKeyChainStore。
FCUUID提供的类方法:
// 每次运行应用都会变 +(NSString *)uuid; //changes each time (no persistent), but allows to keep in memory more temporary uuids +(NSString *)uuidForKey:(id<NSCopying>)key; // 每次运行应用都会变 +(NSString *)uuidForSession; // 重新安装的时候会变 +(NSString *)uuidForInstallation; // 卸载后重装会变 +(NSString *)uuidForVendor; // 抹掉iPhone的时候才会变,适合做唯一标识 +(NSString *)uuidForDevice;
NSUbiquitousKeyValueStore
iOS开发中为了防止用户将app卸载,再安装的时候丢失数据,所以关于apple提供的沙盒本地存储外,还提供了云存储iCloud。iCloud key-value仅适用于保存小数据,如用户偏好、系统设置和一些简单的App状态。如果需要存储大文件、数据库数据等.
iCloud key-value存储限制:
- 每个App每个用户总共最多可存储1MB数据
- key的最大数量是1024
- 每个key的最大长度是64Bytes
- 每个key最大可以存储1MB的value
如果你使用了一个key存储了一个1MB的value,那么存储容量已经到达上限;如果你每个key的都存储1kb的数据,那么你可以使用1000个这样的key存储
特别注意NSData:虽然value可以是NSData数据类型,但是因为空间的关系,苹果并不推荐存储该类型的数据。另外,每次你对data只是做了很小的改动,在传输过程中,依然会把整个data重新上传。 推荐的做法是,在业务层面,将data尽可能分成多个key去存储,避免每次做多余的上传。
用法和NSUserDefaults一样,使用setValue:ForKey:方法存储,但是并不需要调用synchronize方法,调用了反而可能在特定情况下出错:
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; //存储 [store setObject:@"testValue" forKey:@"testKey"]; //读取 [store objectForKey:@"testKey"]; //删除 [store removeObjectForKey:@"testKey"];