人从生下来开始就得上户口,从而获得一个身份证号,从此这个身份证号如影随形,跟这个人永远绑在一起,再也不分离。有些手续办起来,认号不认人,因为这个身份证号是唯一的,而人可能会变,比如整容等各种手段,所以判断是否是这个人,身份证号是一个相对不错的手段。
同样,一台设备,如果要有个准确的判断,以及要知道是不是同一台设备,一个唯一的设备标识符就显得尤为重要。iOS中唯一标识符主要经过下面几个阶段。
IMEI
IMEI(International Mobile Equipment Identity):国际移动设备身份码,在手机组装完成后赋予的一个全球唯一的号码。双卡双待手机有2个通信芯片,相当于2个手机装在1个手机外壳里面。而每个通信芯片都需要有IMEI标识,所以双卡双待手机有2个通信芯片,但是一般系统提供的api获取的是固定的那一个。
iOS2.0提供了获取IMEI的方法,但是iOS5.0之后不允许获取,否则上架时会审核不通过。
UDID
UDID(Unique Device Identifier):苹果 iOS 设备的唯一识别码。iOS6禁止获取。
现在如果要获取UDID/IMEI,可以通过Safari+mobileConfig来获取。
IDFA
IDFA(Identifier for Identifier),即广告标识符,多用于用户的广告追踪,是每台设备的唯一 ID,IDFA 存储在用户的系统上。Apple 是不允许开发者追踪用户设备的,但是为了监控广告效果,在 iOS 6 中提供这个折中方案。获取方法如下:
NSString *idfa = nil;
ASIdentifierManager * adManage = [ASIdentifierManager sharedManager];
if (adManage.advertisingTrackingEnabled) {
idfa = adManage.advertisingIdentifier.UUIDString;
}
return idfa;
但 IDFA 也会发生变化,比如重置系统、还原广告标识符等。同时,用户也可以选择是否禁止广告追踪,操作路径:设置-隐私-广告-限制广告跟踪。限制广告跟踪之后返回的是nil。iOS14之前,默认是关闭的,而且整个系统是统一管理。iOS14之后,每个APP单独申请,默认是打开(限制)的。通过idfa归因逻辑大概如下:
MAC地址
Mac(Medium/Media Access Control):网络设备的物理地址,如果 IMEI 被认为是设备的唯一标识,那么 Mac 就是网络接口唯一标识。iOS7之后获得的Mac地址为一个固定值:02:00:00:00:00:00。获取方法如下:
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_dl.h>
+ (NSString *)getMacAddress {
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");
free(buf);
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)];
free(buf);
return outstring;
}
OpenUDID
openUIDI是由开源库[OpenUDID](https://github.com/ylechelle/OpenUDID)生成的识别码。它不依赖于Mac地址,可以起到以往UDID的作用。但是如果完全删除全部带有该开源库的APP,那么这个id会重新生成,而且和之前的值不同,相当于一台新设备。可惜因为iOS7对剪贴板进行了限制,该库也废掉了。
UUID
UUID(Universally Unique IDentifier):通用唯一识别码。获取方法如下:
[NSUUID UUID]
每次获取的值都会变化,但会保持唯一性。
IDFV
IDFV(identifierForVendor):Vendor 标示符,也被称为厂商标识符。只要用户的设备中没有卸载当前 Vendor 的所有 APP,则不会发生变化。获取方法如下:
[[[UIDevice currentDevice] identifierForVendor] UUIDString]
一般情况,vender由App Store提供的数据决定,如果安装的APP不是从App Store下载,比如通过enterprise包或者通过xcode安装的调试包,vender则由APP的bundle ID计算出来的。这个bundle ID符合reverse-DNS规则。在iOS6及之前,用bundle ID的前两位来生成vender ID,如果bundle ID是个单独的字符串,则由整个bundle ID来生成。在iOS7及以后,用bundle ID的非最后一位来生成vender ID,如果bundle ID是个单独的字符串,则由整个bundle ID来生成。具体见下表:
| bundle ID | iOS 6.x | iOS 7.x |
|---|---|---|
| com.example.app1 | com.example.app1 | com.example.app1 |
| com.example.app2 | com.example.app2 | com.example.app2 |
| com.example.app.app1 | com.example.app.app1 | com.example.app.app1 |
| com.example.app.app2 | com.example.app.app2 | com.example.app.app2 |
| example | example | example |
| 如上表,com.example.app1和com.example.app2有同样的vender ID。 |
数盟ID
APP方可应用数盟可信ID可作为识别设备唯一性的基准标识,不同于安卓的IMEI,MAC,序列号和iOS系统的IDFA等设备信息,它更不 易被篡改,不随Rom等变化改变,实时识别虚拟机环境等,完美兼容安卓10、iOS14平台。
SKAdNetwork
SKAdNetwork是让广告平台在不获取IDFA的前提下,对用户的点击和安装行为提供的一套追踪解决方案。由于Apple的介入,将直接横跨设备与App Store,并且不会把任何设备信息透露给广告主,并且更有利于防作弊。原理如下:
但是SKAdNetwork的应用场景比较单一,目前知道的主要是用来推广归因。