在iOS系統(tǒng)中碍论,獲取設(shè)備唯一標(biāo)識的方法有很多(可直接看第八點(diǎn)句柠,比較靠譜):
一.UDID(Unique Device Identifier)
UDID的全稱是Unique Device Identifier榕暇,它就是蘋果IOS設(shè)備的唯一識別碼依沮,它由40個字符的字母和數(shù)字組成(越獄的設(shè)備通過某些工具可以改變設(shè)備的UDID)恃逻。移動網(wǎng)絡(luò)可利用UDID來識別移動設(shè)備,但是晤锹,從IOS5.0(2011年8月份)開始,蘋果宣布將不再支持用uniqueIdentifier方法獲取設(shè)備的UDID彤委,iOS5以下是可以用的鞭铆。在2013年3月21日蘋果已經(jīng)通知開發(fā)者:從2013年5月1日起,訪問UIDIDs的程序?qū)⒉辉俦粚徍送ㄟ^焦影,替代的方案是開發(fā)者應(yīng)該使用“在iOS 6中介紹的Vendor或Advertising標(biāo)示符”车遂。所以UDID是絕對不能用啦。
二.UUID(Universally Unique Identifier)
UUID是Universally Unique Identifier的縮寫斯辰,中文意思是通用唯一識別碼舶担。它是讓分布式系統(tǒng)中的所有元素,都能有唯一的辨識資訊椒涯,而不需要透過中央控制端來做辨識資訊的指定柄沮。這樣,每個人都可以建立不與其它人沖突的 UUID废岂。在此情況下祖搓,就不需考慮數(shù)據(jù)庫建立時的名稱重復(fù)問題。蘋果公司建議使用UUID為應(yīng)用生成唯一標(biāo)識字符串湖苞。
三.MAC Address
這個MAC地址是指什么拯欧?有什么用?
MAC(Medium/Media Access Control)地址财骨,用來表示互聯(lián)網(wǎng)上每一個站點(diǎn)的標(biāo)識符镐作,采用十六進(jìn)制數(shù)表示,共六個字節(jié)(48位)隆箩。其中该贾,前三個字節(jié)是由IEEE的注冊管理機(jī)構(gòu) RA負(fù)責(zé)給不同廠家分配的代碼(高位24位),也稱為“編制上唯一的標(biāo)識符” (Organizationally Unique Identifier)捌臊,后三個字節(jié)(低位24位)由各廠家自行指派給生產(chǎn)的適配器接口杨蛋,稱為擴(kuò)展標(biāo)識符(唯一性)。
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è)備唯一性寻仗,類似與蘋果設(shè)備的UDID號刃泌,通常的用途有:1)用于一些統(tǒng)計(jì)與分析目的,利用用戶的操作習(xí)慣和數(shù)據(jù)更好的規(guī)劃產(chǎn)品署尤;2)作為用戶ID來唯一識別用戶耙替,可以用游客身份使用app又能在服務(wù)器端保存相應(yīng)的信息,省去用戶名曹体、密碼等注冊過程俗扇。
那么,如何使用Mac地址生成設(shè)備的唯一標(biāo)識呢箕别?主要分三種:
1铜幽、直接使用“MAC Address”
2、使用“MD5(MAC Address)”
3串稀、使用“MD5(Mac Address+bundle_id)”獲得“機(jī)器+應(yīng)用”的唯一標(biāo)識(bundle_id 是應(yīng)用的唯一標(biāo)識)
iOS7之前除抛,因?yàn)镸ac地址是唯一的, 一般app開發(fā)者會采取第3種方式來識別安裝對應(yīng)app的設(shè)備母截。為什么會使用它到忽?在iOS5之前,都是使用UDID的清寇,后來被禁用喘漏。蘋果推薦使用UUID 但是也有諸多問題,從而使用MAC地址华烟。而MAC地址跟UDID一樣翩迈,存在隱私問題,現(xiàn)在蘋果新發(fā)布的iOS7上盔夜,如果請求Mac地址都會返回一個固定 值负饲,那么Mac Address+bundle_id這個值大家的設(shè)備都變成一致的啦搅方,跟UDID一樣相當(dāng)于被禁用。那么绽族,要怎么標(biāo)識設(shè)備唯一呢?
關(guān)于為什么用mac地址作為手機(jī)的唯一標(biāo)識,請參考知乎討論
四.OPEN UDID
OPEN UDID衩藤,沒有用到MAC地址吧慢,同時能保證同一臺設(shè)備上的不同應(yīng)用使用同一個OpenUDID,只要用戶設(shè)備上有一個使用了OpenUDID的應(yīng)用存在時赏表,其他后續(xù)安裝的應(yīng)用如果獲取OpenUDID检诗,都將會獲得第一個應(yīng)用生成的那個。但是根據(jù)貢獻(xiàn)者的代碼和方法瓢剿,和一些開發(fā)者的經(jīng)驗(yàn)逢慌,如果把使用了OpenUDID方案的應(yīng)用全部都刪除,再重新獲取OpenUDID间狂,此時的OpenUDID就跟以前的不一樣攻泼。可見鉴象,這種方法還是不保險忙菠。
五.廣告標(biāo)示符(IDFA-identifierForIdentifier)
廣告標(biāo)示符,是iOS 6中另外一個新的方法纺弊,提供了一個方法advertisingIdentifier牛欢,通過調(diào)用該方法會返回一個NSUUID實(shí)例,最后可以獲得一個UUID淆游,由系統(tǒng)存儲著的傍睹。不過即使這是由系統(tǒng)存儲的,但是有幾種情況下犹菱,會重新生成廣告標(biāo)示符拾稳。如果用戶完全重置系統(tǒng)((設(shè)置程序 -> 通用 -> 還原 -> 還原位置與隱私) ,這個廣告標(biāo)示符會重新生成已亥。另外如果用戶明確的還原廣告(設(shè)置程序-> 通用 -> 關(guān)于本機(jī) -> 廣告 -> 還原廣告標(biāo)示符) 熊赖,那么廣告標(biāo)示符也會重新生成。關(guān)于廣告標(biāo)示符的還原虑椎,有一點(diǎn)需要注意:如果程序在后臺運(yùn)行震鹉,此時用戶“還原廣告標(biāo)示符”,然后再回到程序中捆姜,此時獲取廣 告標(biāo)示符并不會立即獲得還原后的標(biāo)示符传趾。必須要終止程序,然后再重新啟動程序泥技,才能獲得還原后的廣告標(biāo)示符浆兰。
六.Vindor標(biāo)示符 (IDFV-identifierForVendor)
Vindor標(biāo)示符,也是在iOS 6中新增的,跟advertisingIdentifier一樣簸呈,該方法返回的是一個 NSUUID對象榕订,可以獲得一個UUID。如果滿足條件“相同的一個程序里面-相同的vindor-相同的設(shè)備”蜕便,那么獲取到的這個屬性值就不會變劫恒。如果是“相同的程序-相同的設(shè)備-不同的vindor,或者是相同的程序-不同的設(shè)備-無論是否相同的vindor”這樣的情況轿腺,那么這個值是不會相同的两嘴。
七.推送token+bundle_id
推送token+bundle_id的方法:
1、應(yīng)用中增加推送用來獲取token
2族壳、獲取應(yīng)用bundle_id
3憔辫、根據(jù)token+bundle_id進(jìn)行散列運(yùn)算
apple push token保證設(shè)備唯一,但必須有網(wǎng)絡(luò)情況下才能工作仿荆,該方法不依賴于設(shè)備本身贰您,但依賴于apple push,而蘋果push有時候會抽風(fēng)的拢操。
八,利用keyChain和UUID永久獲得設(shè)備的唯一標(biāo)識(目前比較靠譜的方法)
開發(fā)者可以在應(yīng)用第一次啟動時調(diào)用一 次枉圃,然后將該串存儲起來,以便以后替代UDID來使用庐冯。但是孽亲,如果用戶刪除該應(yīng)用再次安裝時,又會生成新的字符串展父,所以不能保證唯一識別該設(shè)備返劲。這就需要各路高手想出各種解決方案。所以栖茉,之前很多應(yīng)用就采用MAC Address篮绿。但是現(xiàn)在如果用戶升級到iOS7(及其以后的蘋果系統(tǒng))后,他們機(jī)子的MAC Address就是一樣的吕漂,沒辦法做區(qū)分亲配,只能棄用此方法,重新使用UUID來標(biāo)識惶凝。如果使用UUID吼虎,就要考慮應(yīng)用被刪除后再重新安裝時的處理。
那么什么是鑰匙串呢:
一苍鲜、在應(yīng)用間利用KeyChain共享數(shù)據(jù)
我們可以把KeyChain理解為一個Dictionary思灰,所有數(shù)據(jù)都以key-value的形式存儲,可以對這個Dictionary進(jìn)行add混滔、update洒疚、get歹颓、delete這四個操作。對于每一個應(yīng)用來說油湖,KeyChain都有兩個訪問區(qū)巍扛,私有區(qū)和公共區(qū)。私有區(qū)是一個sandbox乏德,本程序存儲的任何數(shù)據(jù)都對其他程序不可見电湘。而要想在將存儲的內(nèi)容放在公共區(qū),需要先聲明公共區(qū)的名稱鹅经,官方文檔管這個名稱叫“keychain access group”,聲明的方法是新建一個plist文件怎诫,名字隨便起瘾晃,內(nèi)容如下:
“yourAppID.com.yourCompany.whatever”就是你要起的公共區(qū)名稱,除了whatever字段可以隨便定之外幻妓,其他的都必須如實(shí)填寫蹦误。這個文件的路徑要配置在 Project->build setting->Code Signing Entitlements里,否則公共區(qū)無效肉津,配置好后强胰,須用你正式的證書簽名編譯才可通過,否則xcode會彈框告訴你code signing有問題妹沙。所以偶洋,蘋果限制了你只能同公司的產(chǎn)品共享KeyChain數(shù)據(jù),別的公司訪問不了你公司產(chǎn)品的KeyChain距糖。
二玄窝、保存私密信息
iOS的keychain服務(wù)提供了一種安全的保存私密信息(密碼,序列號悍引,證書等)的方式恩脂,每個ios程序都有一個獨(dú)立的keychain存儲。相對于NSUserDefaults趣斤、文件保存等一般方式俩块,keychain保存更為安全,而且keychain里保存的信息不會因App被刪除而丟失浓领,所以在重裝App后玉凯,keychain里的數(shù)據(jù)還能使用。
在應(yīng)用里使用使用keyChain联贩,我們需要導(dǎo)入Security.framework 壮啊,keychain的操作接口聲明在頭文件SecItem.h里。直接使用SecItem.h里方法操作keychain撑蒜,需要寫的代碼較為復(fù)雜歹啼,為減輕咱們程序員的開發(fā)玄渗,這里封裝了一個方法,
實(shí)現(xiàn)如下:
//
// KKUUIDManager.h
//
#import <Foundation/Foundation.h>
@interface KKUUIDManager : NSObject
+(void)saveUUID:(NSString *)uuid;
+(id)readUUID;
+(void)deleteUUID;
@end
//
// KKUUIDManager.m
//
#import "KKUUIDManager.h"
#import "KKKeyChain.h"
@implementation KKUUIDManager
#define KEY_IN_KEYCHAIN [NSBundle mainBundle].bundleIdentifier
#define KEY_UUID [NSString stringWithFormat:@"%@.uuid", KEY_IN_KEYCHAIN]
+(void)saveUUID:(NSString *)uuid
{
[KKKeyChain save:KEY_IN_KEYCHAIN data:@{KEY_UUID:uuid}];
}
+(id)readUUID
{
NSMutableDictionary *usernameUuidPairs = (NSMutableDictionary *)[KKKeyChain load:KEY_IN_KEYCHAIN];
return [usernameUuidPairs objectForKey:KEY_UUID];
}
+(void)deleteUUID
{
[KKKeyChain delete:KEY_IN_KEYCHAIN];
}
@end
//
// KKKeyChain.h
//
#import <Foundation/Foundation.h>
@interface KKKeyChain : NSObject
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service ;
+ (void)save:(NSString *)service data:(id)data;
+ (id)load:(NSString *)service;
+ (void)delete:(NSString *)service;
@end
//
// KKKeyChain.m
//
#import "KKKeyChain.h"
@implementation KKKeyChain
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge_transfer id)kSecClassGenericPassword,(__bridge_transfer id)kSecClass,
service, (__bridge_transfer id)kSecAttrService,
service, (__bridge_transfer id)kSecAttrAccount,
(__bridge_transfer id)kSecAttrAccessibleAfterFirstUnlock,(__bridge_transfer id)kSecAttrAccessible,
nil];
}
+ (void)save:(NSString *)service data:(id)data {
//Get search dictionary
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Delete old item before add new item
CFDictionaryRef aRef = (__bridge_retained CFDictionaryRef)keychainQuery;
SecItemDelete(aRef/*(__bridge_retained CFDictionaryRef)keychainQuery*/);
//Add new object to search dictionary(Attention:the data format)
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge_transfer id)kSecValueData];
//Add item to keychain with the search dictionary
SecItemAdd(aRef/*(__bridge_retained CFDictionaryRef)keychainQuery*/, NULL);
}
+ (id)load:(NSString *)service {
id ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Configure the search setting
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnData];
[keychainQuery setObject:(__bridge_transfer id)kSecMatchLimitOne forKey:(__bridge_transfer id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((__bridge_retained CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
@try {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge_transfer NSData *)keyData];
} @catch (NSException *e) {
NSLog(@"Unarchive of %@ failed: %@", service, e);
} @finally {
}
}
return ret;
}
+ (void)delete:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery);
}
@end
然后再任意地方調(diào)用以下獲取
NSString *uuid = [KKUUIDManager readUUID];