【iOS】獲取設(shè)備唯一標(biāo)識(shí)符

最近項(xiàng)目中要用到設(shè)備的唯一標(biāo)識(shí)符,于是了解了一下這方面的知識(shí)。設(shè)備的唯一標(biāo)識(shí)符具體可以分為如下幾種:
1、UDID
2、MAC
3、IDFA
4、IDFV
5、UUID

下面我們來(lái)具體分析下每種標(biāo)識(shí)獲取的可行性和使用的利弊:

1、UDID(Unique Device Identifier Description)

UDID 是由子母和數(shù)字組成的40個(gè)字符串的序號(hào),用來(lái)區(qū)別每一個(gè)唯一的iOS設(shè)備,這些編碼看起來(lái)是隨機(jī)的,但實(shí)際上是跟硬件設(shè)備特點(diǎn)相關(guān)聯(lián)的。我們平時(shí)用開發(fā)者賬號(hào)在設(shè)備上安裝測(cè)試應(yīng)用時(shí),往開發(fā)者賬號(hào)上添加的就是設(shè)備的UDID。
iOS 2.0 版本以后,蘋果提供了一個(gè)獲取設(shè)備唯一標(biāo)識(shí)符的方法

NSString *UDID = [[UIDevice currentDevice] uniqueIdentifier] ;

通過(guò)該方法我們可以獲取設(shè)備的序列號(hào),這個(gè)也是目前為止唯一可以確認(rèn)唯一的標(biāo)示符。由于UDID是跟設(shè)備唯一對(duì)應(yīng)的,許多開發(fā)者試圖通過(guò)UDID獲取到用戶的真實(shí)姓名、密碼、地址等隱私數(shù)據(jù)。為了避免引起麻煩,蘋果在iOS 5.0 的時(shí)候,廢除了UDID的代碼獲取權(quán)限?,F(xiàn)在應(yīng)用試圖獲取UDID已被禁止且不允許上架。

當(dāng)然,目前想要獲取UDID也并不是全無(wú)辦法,不過(guò)過(guò)程可能會(huì)復(fù)雜很多,這里就不做贅述了,有興趣的朋友可以參考這篇博客:通過(guò)Safari獲取UDID

2、MAC(Medium/Media Access Control)

Mac地址是用來(lái)表示互聯(lián)網(wǎng)上每一個(gè)站點(diǎn)的標(biāo)識(shí)符,采用十六進(jìn)制數(shù)表示,共六個(gè)字節(jié)(48位)。MAC地址在網(wǎng)絡(luò)上用來(lái)區(qū)分設(shè)備的唯一性,接入網(wǎng)絡(luò)的設(shè)備都有一個(gè)MAC地址,他們肯定都是不同的,是唯一的。一部iPhone上可能有多個(gè)MAC地址,包括WIFI的、SIM的等,但是iTouch和iPad上就有一個(gè)WIFI的,因此只需獲取WIFI的MAC地址就好了,也就是en0的地址。形象的說(shuō),MAC地址就如同我們身份證上的身份證號(hào)碼,具有全球唯一性。這樣就可以非常好的標(biāo)識(shí)設(shè)備唯一性。UDID被禁用后,因?yàn)槊總€(gè)設(shè)備上的Mac地址是唯一的,所以大部分的app開發(fā)者都開始使用Mac地址來(lái)代替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一樣,存在隱私問(wèn)題,所以在 iOS 7.0 之后,Mac地址再次遭到蘋果的無(wú)情封殺。如果使用之前的方法請(qǐng)求Mac地址都會(huì)返回一個(gè)固定值 02:00:00:00:00:00,這一條路再次被堵死。

3、IDFA (Identifier For Advertising)

這是 iOS 6.0中提供的一個(gè)新方法,在同一個(gè)設(shè)備上的所有App都會(huì)取到相同的值,是蘋果專門給各廣告提供商用來(lái)追蹤用戶而設(shè)的。獲取idfa的方法如下:

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

但是IDFA并不是唯一不變的,如果用戶完全重置系統(tǒng)(設(shè)置程序 -> 通用 -> 還原 -> 還原位置與隱私) ,這個(gè)廣告標(biāo)示符會(huì)重新生成。另外如果用戶明確的還原廣告(設(shè)置程序-> 通用 -> 關(guān)于本機(jī) -> 廣告 -> 還原廣告標(biāo)示符) ,那么廣告標(biāo)示符也會(huì)重新生成。在iOS 10.0以后如果用戶打開限制廣告跟蹤(設(shè)置程序-> 通用 -> 關(guān)于本機(jī) -> 廣告 -> 限制廣告跟蹤),則獲取到的IDFA為一個(gè)固定值00000000-0000-0000-0000-000000000000。因此,通過(guò)IDFA也無(wú)法唯一標(biāo)識(shí)一個(gè)設(shè)備。

4、IDFV(Identifier For Vendor)

IDFV是給Vendor標(biāo)識(shí)用戶用的,每個(gè)設(shè)備在所屬同一個(gè)Vendor的應(yīng)用里,都有相同的值。其中的Vendor是指應(yīng)用提供商,準(zhǔn)確的說(shuō),是通過(guò)BundleID的反轉(zhuǎn)的前兩部分進(jìn)行匹配,如果相同就是同一個(gè)Vendor,例如對(duì)于com.abc.app1, com.abc.app2 這兩個(gè)BundleID來(lái)說(shuō),就屬于同一個(gè)Vendor,共享同一個(gè)IDFV的值。當(dāng)然,對(duì)于同一個(gè)設(shè)備不同Vendor的話,獲取到的值是不同的。和IDFA不同的是,IDFV的值是一定能取到的。它是iOS 6中新增的,獲取方法如下:

NSString *strIDFV = [[[UIDevice currentDevice] identifierForVendor] UUIDString];

但是使用IDFV也會(huì)存在一些問(wèn)題。如果用戶將屬于此Vendor的所有App卸載,則IDFV的值會(huì)被重置,即再重裝此Vendor的App,IDFV的值也會(huì)和之前的不同。

5、UUID(Universally Unique Identifier)

UUID是Universally Unique Identifier的縮寫,中文意思是通用唯一識(shí)別碼。它是蘋果提供的一個(gè)獲取大隨機(jī)數(shù)的方法,據(jù)說(shuō)UUID隨機(jī)數(shù)算法得到的數(shù)重復(fù)概率為170億分之一。這樣,每個(gè)人都可以建立不與其它人沖突的 UUID。

1)CFUUID
從iOS 2.0開始,CFUUID就已經(jīng)出現(xiàn)了。它是CoreFoundation包的一部分,因此API屬于C語(yǔ)言風(fēng)格。獲取方法參考如下代碼:

CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);
NSString *cfuuidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfuuid));

2)NSUUID
NSUUID在iOS 6.0中才出現(xiàn),這跟CFUUID幾乎完全一樣,只不過(guò)它是Objective-C接口。通過(guò)下面的代碼可以獲得一個(gè)UUID字符串:

NSString *uuid = [[NSUUID UUID] UUIDString];

蘋果公司建議使用UUID為應(yīng)用生成唯一標(biāo)識(shí)字符串。但是獲得的UUID值系統(tǒng)沒(méi)有存儲(chǔ), 而且每次調(diào)用得到UUID,系統(tǒng)都會(huì)返回一個(gè)新的唯一標(biāo)示符。如果你希望存儲(chǔ)這個(gè)標(biāo)示符,那么需要自己將其存儲(chǔ)到NSUserDefaults, Keychain, Pasteboard或其它地方。

6、總結(jié)

說(shuō)了這么多, 才發(fā)現(xiàn)原來(lái)沒(méi)有一種方法是可行的。沒(méi)錯(cuò), 其實(shí)自從蘋果廢除UDID后, 就不能達(dá)到獲取設(shè)備真正的唯一標(biāo)識(shí)了。因?yàn)檫@些方法中導(dǎo)致獲取的唯一標(biāo)示產(chǎn)生改變的原因, 或是重新調(diào)用方法, 或是重啟設(shè)備, 或是卸載應(yīng)用, 或是還原某些標(biāo)識(shí), 或者刷新系統(tǒng)…

所以, 我們不能達(dá)到從根本上獲取唯一標(biāo)識(shí),只能做到盡可能接近。下面是我自己的一套獲取設(shè)備唯一標(biāo)識(shí)的方法。希望能夠?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獲取失敗的情況,也能最大限度保證同一個(gè)設(shè)備上的不同應(yīng)用獲取到的標(biāo)識(shí)符為同一標(biāo)識(shí)符。除開用戶重置手機(jī)的情況,其他情況下基本都可以保證取到相同的設(shè)備標(biāo)識(shí)符,相對(duì)來(lái)說(shuō)是比較安全的。當(dāng)然,由于使用到了IDFA,在上傳Appstore時(shí)必須在iTunes Connect中的上傳頁(yè)面進(jìn)行相應(yīng)的設(shè)置,否則上傳應(yīng)用審核的時(shí)候會(huì)出現(xiàn)錯(cuò)誤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容