最近項目中要用到設(shè)備的唯一標(biāo)識符肮塞,于是了解了一下這方面的知識襟齿。設(shè)備的唯一標(biāo)識符具體可以分為如下幾種:
1、UDID
2枕赵、MAC
3猜欺、IDFA
4、IDFV
5拷窜、UUID
下面我們來具體分析下每種標(biāo)識獲取的可行性和使用的利弊:
1开皿、UDID(Unique Device Identifier Description)
UDID 是由子母和數(shù)字組成的40個字符串的序號,用來區(qū)別每一個唯一的iOS設(shè)備篮昧,這些編碼看起來是隨機的赋荆,但實際上是跟硬件設(shè)備特點相關(guān)聯(lián)的。我們平時用開發(fā)者賬號在設(shè)備上安裝測試應(yīng)用時懊昨,往開發(fā)者賬號上添加的就是設(shè)備的UDID窄潭。
iOS 2.0 版本以后,蘋果提供了一個獲取設(shè)備唯一標(biāo)識符的方法
NSString *UDID = [[UIDevice currentDevice] uniqueIdentifier] 疚颊;
通過該方法我們可以獲取設(shè)備的序列號狈孔,這個也是目前為止唯一可以確認(rèn)唯一的標(biāo)示符。由于UDID是跟設(shè)備唯一對應(yīng)的材义,許多開發(fā)者試圖通過UDID獲取到用戶的真實姓名均抽、密碼、地址等隱私數(shù)據(jù)其掂。為了避免引起麻煩油挥,蘋果在iOS 5.0 的時候,廢除了UDID的代碼獲取權(quán)限】畎荆現(xiàn)在應(yīng)用試圖獲取UDID已被禁止且不允許上架深寥。
當(dāng)然,目前想要獲取UDID也并不是全無辦法贤牛,不過過程可能會復(fù)雜很多惋鹅,這里就不做贅述了,有興趣的朋友可以參考這篇博客:通過Safari獲取UDID
2殉簸、MAC(Medium/Media Access Control)
Mac地址是用來表示互聯(lián)網(wǎng)上每一個站點的標(biāo)識符闰集,采用十六進制數(shù)表示,共六個字節(jié)(48位)般卑。MAC地址在網(wǎng)絡(luò)上用來區(qū)分設(shè)備的唯一性武鲁,接入網(wǎng)絡(luò)的設(shè)備都有一個MAC地址,他們肯定都是不同的蝠检,是唯一的沐鼠。一部iPhone上可能有多個MAC地址,包括WIFI的、SIM的等饲梭,但是iTouch和iPad上就有一個WIFI的乘盖,因此只需獲取WIFI的MAC地址就好了,也就是en0的地址排拷。形象的說侧漓,MAC地址就如同我們身份證上的身份證號碼,具有全球唯一性监氢。這樣就可以非常好的標(biāo)識設(shè)備唯一性布蔗。UDID被禁用后,因為每個設(shè)備上的Mac地址是唯一的浪腐,所以大部分的app開發(fā)者都開始使用Mac地址來代替UDID纵揍。獲取Mac地址的方法如下:
- (NSString *) getMacAddress
{
int mib[6];
size_t len;
charchar *buf;
unsigned charchar *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 charchar *)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 uppercaseString];
}
但MAC地址跟UDID一樣,存在隱私問題议街,所以在 iOS 7.0 之后泽谨,Mac地址再次遭到蘋果的無情封殺。如果使用之前的方法請求Mac地址都會返回一個固定值 02:00:00:00:00:00特漩,這一條路再次被堵死吧雹。
3、IDFA (Identifier For Advertising)
這是 iOS 6.0中提供的一個新方法涂身,在同一個設(shè)備上的所有App都會取到相同的值雄卷,是蘋果專門給各廣告提供商用來追蹤用戶而設(shè)的。獲取idfa的方法如下:
NSString *idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
但是IDFA并不是唯一不變的蛤售,如果用戶完全重置系統(tǒng)(設(shè)置程序 -> 通用 -> 還原 -> 還原位置與隱私) 憔杨,這個廣告標(biāo)示符會重新生成挤渔。另外如果用戶明確的還原廣告(設(shè)置程序-> 通用 -> 關(guān)于本機 -> 廣告 -> 還原廣告標(biāo)示符) ,那么廣告標(biāo)示符也會重新生成肛根。在iOS 10.0以后如果用戶打開限制廣告跟蹤(設(shè)置程序-> 通用 -> 關(guān)于本機 -> 廣告 -> 限制廣告跟蹤)缀壤,則獲取到的IDFA為一個固定值00000000-0000-0000-0000-000000000000钦睡。因此憔鬼,通過IDFA也無法唯一標(biāo)識一個設(shè)備吉嚣。
4、IDFV(Identifier For Vendor)
IDFV是給Vendor標(biāo)識用戶用的炒嘲,每個設(shè)備在所屬同一個Vendor的應(yīng)用里谈竿,都有相同的值。其中的Vendor是指應(yīng)用提供商摸吠,準(zhǔn)確的說,是通過BundleID的反轉(zhuǎn)的前兩部分進行匹配嚎花,如果相同就是同一個Vendor寸痢,例如對于com.abc.app1, com.abc.app2 這兩個BundleID來說,就屬于同一個Vendor紊选,共享同一個IDFV的值啼止。當(dāng)然道逗,對于同一個設(shè)備不同Vendor的話,獲取到的值是不同的献烦。和IDFA不同的是滓窍,IDFV的值是一定能取到的。它是iOS 6中新增的巩那,獲取方法如下:
NSString *strIDFV = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
但是使用IDFV也會存在一些問題吏夯。如果用戶將屬于此Vendor的所有App卸載,則IDFV的值會被重置即横,即再重裝此Vendor的App噪生,IDFV的值也會和之前的不同。
5东囚、UUID(Universally Unique Identifier)
UUID是Universally Unique Identifier的縮寫跺嗽,中文意思是通用唯一識別碼。它是蘋果提供的一個獲取大隨機數(shù)的方法页藻,據(jù)說UUID隨機數(shù)算法得到的數(shù)重復(fù)概率為170億分之一桨嫁。這樣,每個人都可以建立不與其它人沖突的 UUID份帐。
1)CFUUID
從iOS 2.0開始璃吧,CFUUID就已經(jīng)出現(xiàn)了。它是CoreFoundation包的一部分弥鹦,因此API屬于C語言風(fēng)格肚逸。獲取方法參考如下代碼:
CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);
NSString *cfuuidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfuuid));
2)NSUUID
NSUUID在iOS 6.0中才出現(xiàn),這跟CFUUID幾乎完全一樣彬坏,只不過它是Objective-C接口朦促。通過下面的代碼可以獲得一個UUID字符串:
NSString *uuid = [[NSUUID UUID] UUIDString];
蘋果公司建議使用UUID為應(yīng)用生成唯一標(biāo)識字符串。但是獲得的UUID值系統(tǒng)沒有存儲, 而且每次調(diào)用得到UUID栓始,系統(tǒng)都會返回一個新的唯一標(biāo)示符务冕。如果你希望存儲這個標(biāo)示符,那么需要自己將其存儲到NSUserDefaults, Keychain, Pasteboard或其它地方幻赚。
6禀忆、總結(jié)
說了這么多, 才發(fā)現(xiàn)原來沒有一種方法是可行的。沒錯, 其實自從蘋果廢除UDID后, 就不能達到獲取設(shè)備真正的唯一標(biāo)識了落恼。因為這些方法中導(dǎo)致獲取的唯一標(biāo)示產(chǎn)生改變的原因, 或是重新調(diào)用方法, 或是重啟設(shè)備, 或是卸載應(yīng)用, 或是還原某些標(biāo)識, 或者刷新系統(tǒng)…
所以, 我們不能達到從根本上獲取唯一標(biāo)識,只能做到盡可能接近箩退。下面是我自己的一套獲取設(shè)備唯一標(biāo)識的方法。希望能夠?qū)Υ蠹矣兴鶐椭亚1疚闹徽迟N部分邏輯代碼戴涝,需要查看完整代碼的可以到GitHub下載:GitHub地址
//獲取UQID
+ (NSString *)getUQID
{
//從本地沙盒取
NSString *uqid = [[NSUserDefaults standardUserDefaults] objectForKey:UQID_KEY];
if (!uqid) {
//從keychain取
uqid = (NSString *)[YDKeyChain readObjectForKey:UQID_KEY];
if (uqid) {
[[NSUserDefaults standardUserDefaults] setObject:uqid forKey:UQID_KEY];
[[NSUserDefaults standardUserDefaults] synchronize];
} else {
//從pasteboard取
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
id data = [pasteboard dataForPasteboardType:UQID_KEY];
if (data) {
uqid = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
if (uqid) {
[[NSUserDefaults standardUserDefaults] setObject:uqid forKey:UQID_KEY];
[[NSUserDefaults standardUserDefaults] synchronize];
[YDKeyChain saveObject:uqid forKey:UQID_KEY];
} else {
//獲取idfa
uqid = [self getIDFA];
//idfa獲取失敗的情況,獲取idfv
if (!uqid || [uqid isEqualToString:@"00000000-0000-0000-0000-000000000000"]) {
uqid = [self getIDFV];
//idfv獲取失敗的情況,獲取uuid
if (!uqid) {
uqid = [self getUUID];
}
}
[[NSUserDefaults standardUserDefaults] setObject:uqid forKey:UQID_KEY];
[[NSUserDefaults standardUserDefaults] synchronize];
[YDKeyChain saveObject:uqid forKey:UQID_KEY];
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
NSData *data = [uqid dataUsingEncoding:NSUTF8StringEncoding];
[pasteboard setData:data forPasteboardType:UQID_KEY];
}
}
}
return uqid;
}
這套方案既防止了IDFA獲取失敗的情況啥刻,也能最大限度保證同一個設(shè)備上的不同應(yīng)用獲取到的標(biāo)識符為同一標(biāo)識符奸鸯。除開用戶重置手機的情況,其他情況下基本都可以保證取到相同的設(shè)備標(biāo)識符可帽,相對來說是比較安全的娄涩。當(dāng)然,由于使用到了IDFA映跟,在上傳Appstore時必須在iTunes Connect中的上傳頁面進行相應(yīng)的設(shè)置蓄拣,否則上傳應(yīng)用審核的時候會出現(xiàn)錯誤。