iOS中的设备唯一标识
在iOS7之前,曾经有过很多获取设备唯一标识的方法。但是它们都先后被苹果禁止掉了。这些被禁止掉的包括UDID、Mac地址、OpenUDID。在iOS7之后,我们可以选择的唯一标识有IDFA、IDFV、DeviceToken、UUID四种方案。他们各有利弊,下面对他们进行对比。
IDFA
IDFA是苹果iOS6开始新增的广告标识符,用于给开发者跟踪广告效果用的,可以简单理解为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被淘汰
在iOS14中,广告主或广告网盟将无法继续使用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不含有bundleid信息,那是如何区分app的呢?是因为apns和ios设备通信时通过ios设备上的证书可以区分app,当然这种方案也有bug:可能不同app之间的推送会乱。
- iOS7以后的系统,DeviceToken随app变化。此时apns推送到ios设备上的消息通过devicetoken解析出来的bundleid即可投递给指定app。也就说是从信息量上看:devicetoken=deviceid+bundleid。其中deviceid用于识别iOS设备,bundleid用于识别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?
随着iOS14的发布,Apple推出了两个新的归因和广告监测框架。
- 第一个是ATT(App Tracking Transparency)框架。在该框架下,要想访问用户的IDFA,必须先取得用户的许可。
- 第二个是Apple在2018年推出的SKAdNetwork。这是一种不同的推广活动监测方法,在SKA框架中,用户层级的数据不可用。随着iOS14的推出,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的客户,我们也会提供全方位支持。
以下图表描述了安装验证的流程,AppA指展示广告的来源APP,AppB指用户安装的广告主APP。
请仔细看橘黄色的圆圈,这是这流程图的关键。SKAdNetwork是让广告平台在不获取IDFA的前提下对用户的点击和安装行为提供的一套追踪解决方案。这个层面的追踪已经跳开了设备这一个层面,直接内嵌在了广告平台和Apple Store之间,不再收集用户设备信息。
继续看橘黄色的圆圈,我们的目标是点击到安装都有一个身份证进行追踪是么?那么在SKAdNetwork的解决方案里面,Apple给每一次不同用户的点击行为注入了不同的标签(Ad Signature),这个标签一直伴随到安装结束。橘黄色的标签于广告平台(图二)生成(广告平台需要在Apple注册其平台)、用户点击行为(图三)以及用户安装行为(图四)。这样就撇开了设备层面的信息收集,从而变成了给每一次点击安装行为做了一个身份识别,从而完成追踪
你可以这么理解:
- SKAdNetwork的模式就好比你去电影院看电影,从买票(点击)到入场(安装),这张票(Ad Signature)上没有任何你的个人信息,售票处就好比广告平台,只知道这票在什么卖出去的,多少钱卖出去的,哪个渠道卖出去的,是否观影,什么时候观影。但是售票系统不知道买的人具体的特征,比如有没有买爆米花,年龄,性别,是否带女朋友还是每次换个女朋友看电影等信息。
- IDFA的模式就好比出境跟团游,你的护照、存款证明等(IDFA)被旅行社拿走,从而你的一切隐私可以被旅行社拿出来做各种促销,比如旅行社知道你是否消费了,消费了多少,年龄,性别,之前去过哪些国家等等,从而判断你属于什么类型的用户,进而推送更多的广告给你,无隐私可言。
SKAdNetwork的优势
我们还是看到了一些优势,比如App Store会帮你验证安装的有效性-这让那些欺诈的广告网络的作弊方式可以收敛一点。随着版本更新,未来我们应该会看到更多的优势。
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"];