設備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也會重新生成。
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
參考