iOS唯一設備ID


設備ID,簡單來說就是一串符號(或者數字),用來唯一標識一台硬件設備。

對於APP自身產品而言,使用設備唯一ID可以追蹤到用戶從下載、激活、注冊、使用、流失、回歸的全流程數據,對產品運營工作非常有幫助。

對於精准廣告和個性化推薦而言,可以使用設備ID進行數據收集、然后進行千人千面的精准營銷。

 

IMEI

IMEI是國際移動設備識別碼,一串15位的號碼,每部通過正規渠道銷售的GSM手機均有唯一的IMEI碼。

IMEI碼由GSMA協會統一規划,並授權各地區組織進行分配,一般由運營商存儲在SIM卡中。

因此,與Android手機一樣,iOS也有IMEI碼,可在(設置  -> 通用 -> 關於手機 )中查看。 但在iOS5中已經完全的禁用了它。

 

UDID

UDID(Unique Device Identifier Description)是蘋果IOS設備的唯一識別碼,由40個字符的字母和數字組成。

在很多需要限制一台設備一個賬號的應用中經常會用到。在iOS5中可以獲取到設備的UDID,但在iOS7中已經完全的禁用了它。

iOS7之前編譯的app如果在iOS7上運行,它不會返回設備的UDID,而是會返回一串字符串,以FFFFFFFF開頭,跟着identifierForVendor的十六進制值。

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

 

查看設備UDID

方法1:

(1)將手機通過數據線查到電腦,並在手機上信任此電腦后,就可以在iTune中查看手機的UDID

(2)如下可以看到當前手機的UDID(注:如顯示為序列號、ECID、型號識別符等,請點擊切換到UDID)

 

注:新的Mac系統(如:macOS Catalina  版本:10.15.5)已經去掉了iTune了,可從這里查看手機的UDID

 

方法2:將手機通過數據線查到電腦,並在手機上信任此電腦后,在Xcode菜單“Window” -- “Devices and Simulators”的Devices面板中查看手機的UDID

 

當然,目前想要獲取UDID也並不是全無辦法,不過過程非常復雜   有興趣的朋友可以參考這篇博客:通過Safari獲取UDID

 

OpenUDID

由於UDID在iOS7中完全的禁用,OpenUDID成為了當時使用最廣泛的開源UDID替代方案 

OpenUDID利用了一個非常巧妙的方法在不同程序間存儲標示符 — 在粘貼板中用了一個特殊的名稱來存儲標示符。通過這種方法,別的程序(同樣使用了OpenUDID)知道去什么地方獲取已經生成的標示符(而不用再生成一個新的)。

而且根據貢獻者的代碼和方法,和一些開發者的經驗,如果把使用了OpenUDID方案的應用全部都刪除,再重新獲取OpenUDID,此時的OpenUDID就跟以前的不一樣。可見,這種方法還是不保險。

值得一提是:OpenUDID庫早已經棄用了, 在其官方的博客中也指明了, 停止維護OpenUDID的原因是為了更好的向蘋果的舉措靠攏。

 

UUID

UUID(Universally Unique IDentifier):通用唯一識別碼。它是蘋果提供的一個獲取大隨機數的方法。形如:68753A44-4D6F-1226-9C60-0050E4C00067

據說UUID隨機數算法得到的數重復概率為170億分之一。這樣,每個人都可以建立不與其它人沖突的 UUID。

CFUUID

從iOS2.0開始,CFUUID就已經出現了。它是CoreFoundatio包的一部分,因此API屬於C語言風格。CFUUIDCreate 方法用來創建CFUUIDRef,並且可以獲得一個相應的NSString。

每次調用CFUUIDCreate,系統都會返回一個新的CFUUID,但是它會保持唯一性。

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

NSUUID

NSUUID在iOS 6中才出現,與CFUUID完全一樣,只不過它是Objective-C接口。同樣,NSUUID 每次獲取的值都會發生變化,但是它會保持唯一性。

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

 

IDFV

IDFV(Identifier For Vendor)是iOS 6.0系統新增用於替換UDID的接口。是給Vendor標識用戶用的,每個設備在所屬同一個Vender的應用里,都有相同的值。形如:95955F33-BFBD-48BA-A630-866D2DAE482D

其中的Vender是指應用提供商。iOS6.x是通過BundleID前兩部分來匹配,iOS7.x是通過除了最后一個部分來匹配。如果相同就是同一個Vender,共享同一個idfv的值。

具體詳見:identifierForVendor

 

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

和idfa不同的是,idfv的值是一定能取到的,所以非常適合於作為內部用戶行為分析的主id,來標識用戶,替代OpenUDID。

如果用戶將屬於此Vender的所有App卸載,則idfv的值會被重置,即再重裝此Vender的App,idfv的值和之前不同。

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

 

IDFA

IDFA是iOS 6.0系統新增廣告標示符,適用於對外:例如廣告推廣,換量等跨應用的用戶追蹤等。

在同一個設備上的所有App都會取到相同的值,是蘋果專門給各廣告提供商用來追蹤用戶而設的。形如:9C287922-EE26-4501-94B5-DDE6F83E1475

但如果用戶完全重置系統(設置  -> 通用 -> 還原 -> 還原位置與隱私) ,這個廣告標示符會重新生成。

在iOS 10.0之前,用戶開啟了“限制廣告跟蹤”(設置 -> 隱私- > 廣告 -> 限制廣告跟蹤)之后,商家一樣可以獲取idfa,只不過與之前的不一樣了。

在iOS 10.0及以后,開啟了該功能, 獲取到的idfa為一個固定值00000000-0000-0000-0000-000000000000;同樣每次開啟再關閉,相應的idfa也會重新生成。

具體詳見:advertisingIdentifier

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

 

SimulateIDFA

由於IDFA在iOS 10.0及以后版本中,可能無法獲取,因此有人開發了IDFA的替代方案SimulateIDFA。

SimulateIDFA是一個github開源庫,它會根據一堆設備信息(每個app獲取的值都是一樣的)生成的一個MD5值。用於標志不同設備。與IDFA格式一樣,形如:626363D0-90D4-06BF-C281-384E4E69D3E2

前16位626363D0-90D4-06BF是由比較穩定的參數組合獲得(如下),這前16位只有在系統升級的情況下才會變。

系統版本(9.3.2// 系統升級會變
硬件信息(N53AP,iPhone6,2,中國移動46002,1048576000)
coreServices文件創建更新時間(2015-08-07 23:53:00 +0000,2016-06-07 23:53:09 +0000)  // 系統升級會變
系統容量(12266725376)  // 系統升級會變

后16位C281-384E4E69D3E2 由一些比較容易被改變的參數組合生成,比較常見的值變化情況是系統重新啟動。

系統開機時間(1473301191去掉后面的4位數 147330)  // 系統重啟會變
國家代碼(CN)
本地語言(zh-Hans-CN)
設備名稱(XXXX)

在需要獲取 SimulateIDFA的地方調用代碼

NSString *simulateIDFA = [SimulateIDFA createSimulateIDFA];

 

OpenIDFA

由於IDFA在iOS 10.0及以后版本中,可能無法獲取,同樣,有人開發了IDFA的替代方案OpenIDFA。

OpenIDFA是一個github開源庫,與SimulateIDFA一樣,同樣是IDFA的一種替代方案。

OpenIDFA是對下面的參數組合進行MD5

系統開機時間(1473241127 減去后四位值為 147324)
系統容量(29230571520)
系統版本(9.3.4)
機型(N78AP,iPod5,1)
國家代碼(CN)
本地語言(zh-Hans-CN)
一些預裝的App  // 由於用的是canOpenURL這個接口,iOS9就已經廢了
時區(Asia/Shanghai)
當天時間(160804, 16年8月4日)  // 這個值是其每天值都會變化的原因

 

與SimulateIDFA相比,OpenIDFA 有一些限制,生成的IDFA會每天變化,在一些極端條件下重復率比較高。

 

MAC地址

MAC地址在網絡上用來區分設備的唯一性,接入網絡的設備都有一個MAC地址,他們肯定都是不同的,是唯一的。

一部iPhone上可能有多個MAC地址,包括WIFI的、SIM的等,但是iTouch和iPad上就有一個WIFI的,因此只需獲取WIFI的MAC地址就好了,也就是en0的地址。

MAC地址就如同我們身份證上的身份證號碼,具有全球唯一性。

- (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];  
}

 

但在iOS 7.0之后,如果請求Mac地址都會返回一個固定值:02:00:00:00:00:00

 

herody網友實現的UQID

詳見其在github上開源項目:UQIDDemo

 

使用KeyChain來提升持久性

上面的設備ID方案,可保證唯一性,但在持久性上存在或多或少的問題。為了最大限度地維持設備ID的持久性,可以借助KeyChain來保存第一次生成出來的設備ID,然后下次直接從KeyChain讀取該ID

iOS系統中有一個KeyChain(類似於windows的注冊表),每個程序都可以往KeyChain中記錄數據,而且只能讀取到自己程序記錄在KeyChain中的數據。

詳細特點如下:

1. KeyChain與在App的sandbox不一樣,即使刪除了App,資料依然保存在KeyChain中,如果重新安裝了App,還可以從KeyChain中獲取數據

2. KeyChain的數據可以用group的方式,讓程序可以在App間共享。共享的條件如下:

① 相同的bundle id組:比如這里有兩個應用程序: A應用程序使用的provision對應的bundle id是com.jaybin.keychain1,B應用程序使用的provision對應的 bundle id是com.jaybin.keychain2 。那么這兩個應用程序就可以共享keychain數據。

② 應用程序需要打開Keychain Sharing

3. keychain的數據以加密的方式存儲在設備中

 

因此就算我們程序刪除掉,系統經過升級以后再安裝回來,依舊可以獲取到與之前一致的設備ID(系統還原、刷機除外)

+ (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;
}

 

注:KeychainItemWrapper工具類詳見:github

 

參考

iOS獲取設備ID總結

iOS 獲取設備唯一識別碼 IDFV+keychain

iOS獲取設備UUID和IDFA

iOS Device ID 的前世今生

iOS獲取設備的唯一標識的方法總結以及最好的方法

【iOS】獲取設備唯一標識符

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM