iOS开发iOSIOS开发者学习笔记

iOS获取设备UUID和IDFA

2017-04-27  本文已影响7273人  iYeso

一:

1.1 :UDID

简介:UDID的全称是Unique Device Identifier,顾名思义,它就是苹果IOS设备的唯一识别码,它由40个字符的字母和数字组成。在很多需要限制一台设备一个账号的应用中经常会用到。在iOS5中可以获取到设备的UDID,iOS7中已经完全的禁用了它。iOS7之前的使用了的app如果在iOS7上运行,它不会返回设备的UDID,而是会返回一串字符串,以FFFFFFFF开头,跟着identifierForVendor的十六进制值。

获取:[[UIDevice currentDevice] uniqueIdentifier]

废弃:iOS6

1.2 IDFV

简介:iOS 6.0系统新增用于替换uniqueIdentifier的接口。是给Vendor标识用户用的,每个设备在所属同一个Vender的应用里,都有相同的值。其中的Vender是指应用提供商,但准确点说,是通过BundleID的DNS反转的前两部分进行匹配,如果相同就是同一个Vender,例如对于com.somecompany.appone,com.somecompany.apptwo这两个BundleID来说,就属于同一个Vender,共享同一个idfv的值。和idfa不同的是,idfv的值是一定能取到的,所以非常适合于作为内部用户行为分析的主id,来标识用户,替代OpenUDID。如果用户将属于此Vender的所有App卸载,则idfv的值会被重置,即再重装此Vender的App,idfv的值和之前不同。

获取:[[[UIDevice currentDevice] identifierForVendor] UUIDString]

适用:iOS6.0+

例子:95955F33-BFBD-48BA-A630-866D2DAE482D

1.3 IDFA

简介:广告标示符,适用于对外:例如广告推广,换量等跨应用的用户追踪等。但如果用户完全重置系统((设置程序 -> 通用 -> 还原 -> 还原位置与隐私) ,这个广告标示符会重新生成。另外如果用户明确的还原广告(设置程序-> 通用 -> 关于本机 -> 广告 -> 还原广告标示符) ,那么广告标示符也会重新生成。注意:如果程序在后台运行,此时用户“还原广告标示符”,然后再回到程序中,此时获取广 告标示符并不会立即获得还原后的标示符。必须要终止程序,然后再重新启动程序,才能获得还原后的广告标示符。在同一个设备上的所有App都会取到相同的值,是苹果专门给各广告提供商用来追踪用户而设的,
用户可以在 设置 -> 隐私 -> 广告追踪 里重置此id的值,或限制此id的使用。

获取:[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];

适用:iOS6.0+

例子:9C287922-EE26-4501-94B5-DDE6F83E1475

1.4 MAC地址

简介:MAC地址在网络上用来区分设备的唯一性,接入网络的设备都有一个MAC地址,他们肯定都是不同的,是唯一的。一部iPhone上可能有多个MAC地址,包括WIFI的、SIM的等,但是iTouch和iPad上就有一个WIFI的,因此只需获取WIFI的MAC地址就好了,也就是en0的地址。MAC地址就如同我们身份证上的身份证号码,具有全球唯一性。但在iOS7之后,如果请求Mac地址都会返回一个固定值。

注意:由于idfa会出现取不到的情况,故绝不可以作为业务分析的主id,来识别用户。 比如开启限制广告追踪
废弃:iOS7.0+

获取:

- (NSString *)macAddress  
{  

    int                 mib[6];  
    size_t              len;  
    char                *buf;  
    unsigned char       *ptr;  
    struct if_msghdr    *ifm;  
    struct sockaddr_dl  *sdl;  

    mib[0] = CTL_NET;  
    mib[1] = AF_ROUTE;  
    mib[2] = 0;  
    mib[3] = AF_LINK;  
    mib[4] = NET_RT_IFLIST;  

    if ((mib[5] = if_nametoindex("en0")) == 0) {  
        printf("Error: if_nametoindex error/n");  
        return NULL;  
    }  

    if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {  
        printf("Error: sysctl, take 1/n");  
        return NULL;  
    }  

    if ((buf = malloc(len)) == NULL) {  
        printf("Could not allocate memory. error!/n");  
        return NULL;  
    }  

    if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {  
        printf("Error: sysctl, take 2");  
        return NULL;  
    }  

    ifm = (struct if_msghdr *)buf;  
    sdl = (struct sockaddr_dl *)(ifm + 1);  
    ptr = (unsigned char *)LLADDR(sdl);  
    NSString *outstring = [NSString stringWithFormat:@"%02x:%02x:%02x:%02x:%02x:%02x", *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];  

    NSLog(@"outString:%@", outstring);  

    free(buf);  

    return [outstring uppercaseString];  
}
1.5 KeyChain

简介:iOS整个系统有一个KeyChain,每个程序都可以往KeyChain中记录数据,而且只能读取到自己程序记录在KeyChain中的数据。而且就算我们程序删除掉,系统经过升级以后再安装回来,依旧可以获取到与之前一致的UDID(系统还原、刷机除外)。因此我们可以将UUID的字符串存储到KeyChain中,然后下次直接从KeyChain获取UUID字符串。(本示例中使用KeychainItemWrapper工具类)

获取

+ (NSString *)UUID {
    KeychainItemWrapper *keyChainWrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"MYAppID" accessGroup:@"com.test.app"];
    NSString *UUID = [keyChainWrapper objectForKey:(__bridge id)kSecValueData];

    if (UUID == nil || UUID.length == 0) {
        UUID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
        [keyChainWrapper setObject:UUID forKey:(__bridge id)kSecValueData];
    }

    return UUID;
}
1.6 AppleAccount

简介:虽然苹果在iOS6中禁用了获取uuid的方式,但是只要你研究下就知道这个API只是私有化了,使用私有API还是可以获取设备的uuid。但是这个方面也面临着风险:比如API变更以及AppStore审核问题,但是在越狱设备上你还是可以尽情享用的。

类:AADeviceInfo(dump出头文件)

@class NSObject<OS_dispatch_semaphore>, APSConnection, NSData;

@interface AADeviceInfo : NSObject {

APSConnection *_apsConnection;

BOOL _tokenDone;

NSData *_token;

NSObject<OS_dispatch_semaphore> *_tokenSema;

}

+ (id)userAgentHeader;

+ (id)signatureWithDictionary:(id)arg1;

+ (id)apnsToken;

+ (id)serialNumber;

+ (id)clientInfoHeader;

+ (id)appleIDClientIdentifier;

+ (id)productVersion;

+ (id)osVersion;

+ (id)udid;

+ (id)infoDictionary;

- (id)wifiMacAddress;

- (id)regionCode;

- (id)deviceClass;

- (id)osName;

- (id)productType;

- (id)apnsToken;

- (id)serialNumber;

- (id)deviceInfoDictionary;

- (id)appleIDClientIdentifier;

- (id)productVersion;

- (id)osVersion;

- (id)udid;

- (id)init;

- (void).cxx_destruct;

- (id)buildVersion;

@end

获取:[AADeviceInfo udid]

使用方法:在项目中将真机上的AppleAccount.framework框架导出,引入Xcode工程中,利用runtime或者直接使用该类就行。
(细节补充:导出AppleAccount.framework后,进入AppleAccount.framework的根目录,新建Headers文件夹,然后将dump出的头文件放在Headers目录,就可以像引用第三方framework一样在项目中使用)

二: iOS10 获取idfa的坑

ios10更新之后一旦开启了 设置->隐私->广告->限制广告跟踪之后 获取到的idfa将会是一串00000 跟mac地址一个尿性,而且每次开启在关闭之后 相应的idfa也会重新生成,相当于还原了一次广告标识符。

获取idfa的方法:

#import <AdSupport/AdSupport.h>

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

ios10 之后最好加一个判断[[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled] 返回值是BOOL值 如果返回的YES说明没有 “开启限制广告跟踪”,可以获取到正确的idfa 如果返回的是NO,说明等待你的就是一串00000000000

三: IDFA的前世今生

为了保护用户隐私,早在2012年就不再允许其生态中的玩家获取用户的唯一标识符,但是商家在移动端打广告的时候又希望能监控到每一次广告投放的效果,因此,苹果想出了折中的办法,就是提供另外一套和硬件无关的标识符,用于给商家监测广告效果,同时用户可以在设置里改变这串字符,导致商家没有办法长期跟踪用户行为。这个就叫做广告标识符(IDFA),设置路径是“设置->隐私->广告->还原广告标识符”,如下图所示(iOS9)


因为这个IDFA不是唯一的,所以一开始行业内是很抵触的,想方设法去获取UDID(跟手机绑定的,用户不能改变),引起苹果大怒,在13年时禁止所有App获取UDID,否则不能上架,也正因为其生态的封闭性,才能迫使大家就范。虽然IDFA不是唯一的,但是毕竟胜过没有,况且也没有多少用户会去更改。因此,经过几番争斗,IDFA已经成为通用的iPhone用户标识符,这个过程分为6个阶段,我用下图总结



然而在今年iOS10推出后,广告界大为震惊,因为苹果推出了“限制广告追踪”功能,设置的路径和iOS9一致。可能细心的人注意到了,这个功能并非iOS10独有啊,在之前的版本中也一样存在。不过经过实际的测试,在iOS10之前,即使用户打开这个功能,商家一样可以获取IDFA,只不过与之前的不一样了,每次切换这个开关与点击“还原广告标识符”的效果一样。而iOS10就不一样了,当用户打开这个功能后,商家只能获取到一连串无意义的0,这才是广告界大为震惊的原因所在


四: iOS10 IDFA 获取不到问题解决
4.1 限制广告跟踪背景

中文说明文档

Important

In iOS 10.0 and later, the value of advertisingIdentifier is all 
zeroes when the user has limited ad tracking.

也就是说在iOS10上,用户如果开启了 限制广告跟踪 , 获取的idfa将是一串00000000-0000-0000-0000-000000000000

4.2 SimulateIDFA

SimulateIDFA是根据一堆设备信息(每个app获取的值都是一样的)生成的一个MD5值。用于标志不同设备。

#######4.2.1 使用:

CoreTelephony.framework
https://github.com/youmi/SimulateIDFA (下载代码)

在需要获取 SimulateIDFA的地方调用代码:

NSString *simulateIDFA = [SimulateIDFA createSimulateIDFA];

simulateIDFA的格式跟IDFA的格式一样

626363D0-90D4-06BF-C281-384E4E69D3E2

#######4.2.2 生成原理
生成的MD5值分两部分。

626363D0-90D4-06BF-C281-384E4E69D3E2 为例:

前16位626363D0-90D4-06BF是由比较稳定的参数组合获得,这前16位只有在系统升级的情况下才会变。

后16位C281-384E4E69D3E2 由 一些比较容易被改变的参数组合生成,比较常见的值变化情况是系统重新启动。

系统版本(9.3.2)、
硬件信息(N53AP,iPhone6,2,中国移动46002,1048576000)、
coreServices文件创建更新时间(2015-08-07 23:53:00 +0000,2016-06-07 23:53:09 +0000),
系统容量(12266725376)

这里有一些信息是升级的时候会变的,系统版本、coreServices文件创建更新时间、系统容量

系统开机时间(1473301191去掉后面的4位数 147330)、
国家代码(CN)、
本地语言(zh-Hans-CN)、
设备名称(XXXX)

这里的参数都是比较容易变化的,系统重启离上次重启有10000秒的话会变,其他参数在设置里面可以修改

4.3 SimulateIDFA与OpenIDFA对比

OpenIDFA 是 Yann Lechelle的一个开源库。同是IDFA的替换方案

#######4.3.1 生成的ID重复的概率对比
假设一个情况。一天内某个国家有10000000(1千万)台相同型号的设备升级到同一个系统。

系统开机时间(1473241127 减去后四位值为 147324)、系统容量(29230571520)、
系统版本(9.3.4)、机型(N78AP,iPod5,1)、国家代码(CN)、本地语言(zh-Hans-CN)、
一些预装的App(由于用的是canOpenURL这个接口,iOS9就已经废了)、时区(Asia/Shanghai)、
当天时间(160804, 16年8月4日,这个值是他每天值都会变化的原因)

一天内可能的值为系统容量的误差(10000000)。 ps: 系统启动时间在这种情况下对重复率的降低没起到作用,因为OpenIDFA是减去了系统启动时间的后4位来计算的。同理当天时间也是。

设备a的值为 K,那么设备b的值同为K的可能性为: 1/10000000. 总共有 10000000台设备。因此,这10000000设备中有与a设备的值同为K的可能性为 1/10000000 x 10000000 = 1

#######4.3.2 时效性对比

#######4.3.3 总结:
OpenIDFA 有一些限制,生成的IDFA会每天变化,在一些极端条件下重复率比较高。 SimulateIDFA在这方面有更好的表现

五: iOS 提交审核之IDFA的介绍

在我们提交程序进行审核的时候,最后会有两个选项供我们选择,一个是Export Compliance(该选项主要是说你的程序设计是否使用了加密,我一般上传的时候都选择No,这个根据你项目实际情况来选择.);另一个就是Advertising Identifier(广告标示符).

#######5.1 检查是否使用IDFA

检查我们项目中是否使用广告标示符,其实就是查看我们-

(1)打开终端cd到要检查的文件的目录;
(2)执行命令:grep -r advertisingIdentifier .(注意别少了点);

分别对我的项目中和ShareSDK里面进行了检查:


#######5.2 各个选项的含义

#######5.3 总结

参考资料:
http://www.jianshu.com/p/e222ff751a97
ios10 获取idfa的坑
手机更新到Ios10后获取的idfa全是0?
http://www.cnblogs.com/zxykit/p/5320259.html
iOS10 IDFA 获取不到问题解决
http://www.ithao123.cn/content-8688001.html
http://blog.csdn.net/kaitiren/article/details/52562556
iOS 提交审核之IDFA的介绍

上一篇下一篇

猜你喜欢

热点阅读