ios使用KeyChain获取唯一不变的udid
本文是iOS7系列文章第一篇文章,主要介紹使用KeyChain保存和獲取APP數(shù)據(jù),解決iOS7上獲取不變UDID的問(wèn)題。并給出一個(gè)獲取UDID的工具類(lèi),使用方便,只需要替換兩個(gè)地方即可。
?
一、iOS不用版本獲取UDID的方法比較
1)iOS 5.0
iOS 2.0版本以后UIDevice提供一個(gè)獲取設(shè)備唯一標(biāo)識(shí)符的方法uniqueIdentifier,通過(guò)該方法我們可以獲取設(shè)備的序列號(hào),這個(gè)也是目前為止唯一可以確認(rèn)唯一的標(biāo)示符。好景不長(zhǎng),因?yàn)樵撐ㄒ粯?biāo)識(shí)符與手機(jī)一一對(duì)應(yīng),蘋(píng)果覺(jué)得可能會(huì)泄露用戶隱私,所以在 iOS 5.0之后該方法就被廢棄掉了。
而且蘋(píng)果做的更狠,今年5月份以后提交App Store的產(chǎn)品都不允許再用uniqueIdentifier接口,甚至有些朋友因?yàn)榇a中有UDID還被打回來(lái),看來(lái)這條路是被封死了。
?
2)iOS 6.0
iOS 6.0系統(tǒng)新增了兩個(gè)用于替換uniqueIdentifier的接口,分別是:identifierForVendor,advertisingIdentifier。
identifierForVendor接口的官方文檔介紹如下:
The value of this property is the same for apps that come from the same vendor running on the same device. A different value is returned for apps on the same device that come from different vendors, and for apps on different devices regardless of vendor.The value of this property may be nil if the app is running in the background, before the user has unlocked the device the first time after the device has been restarted. If the value is nil, wait and get the value again later.The value in this property remains the same while the app (or another app from the same vendor) is installed on the iOS device. The value changes when the user deletes all of that vendor’s apps from the device and subsequently reinstalls one or more of them. Therefore, if your app stores the value of this property anywhere, you should gracefully handle situations where the identifier changes.大概意思就是“同一開(kāi)發(fā)商的APP在指定機(jī)器上都會(huì)獲得同一個(gè)ID。當(dāng)我們刪除了某一個(gè)設(shè)備上某個(gè)開(kāi)發(fā)商的所有APP之后,下次獲取將會(huì)獲取到不同的ID?!?也就是說(shuō)我們通過(guò)該接口不能獲取用來(lái)唯一標(biāo)識(shí)設(shè)備的ID,問(wèn)題總是難不倒聰明的程序員,于是大家想到了使用WiFi的mac地址來(lái)取代已經(jīng)廢棄了的uniqueIdentifier方法。具體的方法晚上有很多,大家感興趣的可以自己找找,這兒提供一個(gè)網(wǎng)址:?http://stackoverflow.com/questions/677530/how-can-i-programmatically-get-the-mac-address-of-an-iphone
?
3)iOS 7.0
iOS 7中蘋(píng)果再一次無(wú)情的封殺mac地址,使用之前的方法獲取到的mac地址全部都變成了02:00:00:00:00:00。有問(wèn)題總的解決啊,于是四處查資料,終于有了思路是否可以使用KeyChain來(lái)保存獲取到的唯一標(biāo)示符呢,這樣以后即使APP刪了再裝回來(lái),也可以從KeyChain中讀取回來(lái)。有了方向以后就開(kāi)始做,看關(guān)于KeyChain的官方文檔,看官方使用KeyChain的Demo,大概花了一下午時(shí)間,問(wèn)題終于解決了。?
?
二、KeyChain介紹
? 我們搞iOS開(kāi)發(fā),一定都知道OS X里面的KeyChain(鑰匙串),通常要鄉(xiāng)鎮(zhèn)及調(diào)試的話,都得安裝證書(shū)之類(lèi)的,這些證書(shū)就是保存在KeyChain中,還有我們平時(shí)瀏覽網(wǎng)頁(yè)記錄的賬號(hào)密碼也都是記錄在KeyChain中。iOS中的KeyChain相比OS X比較簡(jiǎn)單,整個(gè)系統(tǒng)只有一個(gè)KeyChain,每個(gè)程序都可以往KeyChain中記錄數(shù)據(jù),而且只能讀取到自己程序記錄在KeyChain中的數(shù)據(jù)。iOS中Security.framework框架提供了四個(gè)主要的方法來(lái)操作KeyChain:
// 查詢(xún) OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result);// 添加 OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result);// 更新KeyChain中的Item OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate);// 刪除KeyChain中的Item OSStatus SecItemDelete(CFDictionaryRef query)這四個(gè)方法參數(shù)比較復(fù)雜,一旦傳錯(cuò)就會(huì)導(dǎo)致操作KeyChain失敗,這塊兒文檔中介紹的比較詳細(xì),大家可以查查官方文檔Keychain Services Reference。
前面提到了每個(gè)APP只允許訪問(wèn)自己在KeyChain中記錄的數(shù)據(jù),那么是不是就沒(méi)有別的辦法訪問(wèn)其他APP存在KeyChain的數(shù)據(jù)了?
蘋(píng)果提供了一個(gè)方法允許同一個(gè)發(fā)商的多個(gè)APP訪問(wèn)各APP之間的途徑,即在調(diào)SecItemAdd添加數(shù)據(jù)的時(shí)候指定AccessGroup,即訪問(wèn)組。一個(gè)APP可以屬于同事屬于多個(gè)分組,添加KeyChain數(shù)據(jù)訪問(wèn)組需要做一下兩件事情:
a、在APP target的bulibSetting里面設(shè)置Code Signing Entitlements,指向包含AceessGroup的分組信息的plist文件。該文件必須和工程文件在同一個(gè)目錄下,我在添加訪問(wèn)分組的時(shí)候就因?yàn)閜list文件位置問(wèn)題,操作KeyChain失敗,查找這個(gè)問(wèn)題還花了好久的時(shí)間。
b、在工程目錄下新建一個(gè)KeychainAccessGroups.plist文件,該文件的結(jié)構(gòu)中最頂層的節(jié)點(diǎn)必須是一個(gè)名為“keychain-access-groups”的Array,并且該Array中每一項(xiàng)都是一個(gè)描述分組的NSString。對(duì)于String的格式也有相應(yīng)要求,格式為:"AppIdentifier.com.***",其中APPIdentifier就是你的開(kāi)發(fā)者帳號(hào)對(duì)應(yīng)的ID。
c、在代碼中往KeyChain中Add數(shù)據(jù)的時(shí)候,設(shè)置kSecAttrAccessGroup,代碼如下:
NSString *accessGroup = [NSString stringWithUTF8String:"APPIdentifier.com.cnblogs.smileEvday"];if (accessGroup != nil){ #if TARGET_IPHONE_SIMULATOR// Ignore the access group if running on the iPhone simulator.//// Apps that are built for the simulator aren't signed, so there's no keychain access group// for the simulator to check. This means that all apps can see all keychain items when run// on the simulator.//// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the// simulator will return -25243 (errSecNoAccessForItem). #else[dictForQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup]; #endif}這段代碼是從官方的Demo中直接拷貝過(guò)來(lái)的,根據(jù)注釋我們可以看到,模擬器是不支持AccessGroup的,所以才行了預(yù)編譯宏來(lái)選擇性添加。
注:appIdentifer就是開(kāi)發(fā)者帳號(hào)的那一串標(biāo)識(shí),如下圖所示:
?
打開(kāi)xcode的Organizer,選擇Device選項(xiàng)卡,連接設(shè)備就可以看到設(shè)備上安裝的開(kāi)發(fā)者賬號(hào)描述文件列表,其中第五列最開(kāi)始的10個(gè)字符即為App Identifier,這塊兒前面寫(xiě)的不是很清楚,好多朋友加我qq問(wèn)我,今天特地補(bǔ)上。
?
三、使用KeyChain保存和獲取UDID
說(shuō)了這么多終于進(jìn)入正題了,如何在iOS 7上面獲取到不變的UDID。我們將第二部分所講的知識(shí)直接應(yīng)用進(jìn)來(lái)就可以了輕松達(dá)到我們要的效果了,下面我們先看看往如何將獲取到的identifierForVendor添加到KeyChain中的代碼。
+ (BOOL)settUDIDToKeyChain:(NSString*)udid {NSMutableDictionary *dictForAdd = [[NSMutableDictionary alloc] init];[dictForAdd setValue:(id)kSecClassGenericPassword forKey:(id)kSecClass];[dictForAdd setValue:[NSString stringWithUTF8String:kKeychainUDIDItemIdentifier] forKey:kSecAttrDescription];[dictForAdd setValue:@"UUID" forKey:(id)kSecAttrGeneric];// Default attributes for keychain item.[dictForAdd setObject:@"" forKey:(id)kSecAttrAccount];[dictForAdd setObject:@"" forKey:(id)kSecAttrLabel];// The keychain access group attribute determines if this item can be shared// amongst multiple apps whose code signing entitlements contain the same keychain access group.NSString *accessGroup = [NSString stringWithUTF8String:kKeyChainUDIDAccessGroup];if (accessGroup != nil){ #if TARGET_IPHONE_SIMULATOR// Ignore the access group if running on the iPhone simulator.//// Apps that are built for the simulator aren't signed, so there's no keychain access group// for the simulator to check. This means that all apps can see all keychain items when run// on the simulator.//// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the// simulator will return -25243 (errSecNoAccessForItem). #else[dictForAdd setObject:accessGroup forKey:(id)kSecAttrAccessGroup]; #endif}const char *udidStr = [udid UTF8String];NSData *keyChainItemValue = [NSData dataWithBytes:udidStr length:strlen(udidStr)];[dictForAdd setValue:keyChainItemValue forKey:(id)kSecValueData];OSStatus writeErr = noErr;if ([SvUDIDTools getUDIDFromKeyChain]) { // there is item in keychain [SvUDIDTools updateUDIDInKeyChain:udid];[dictForAdd release];return YES;}else { // add item to keychainwriteErr = SecItemAdd((CFDictionaryRef)dictForAdd, NULL);if (writeErr != errSecSuccess) {NSLog(@"Add KeyChain Item Error!!! Error Code:%ld", writeErr);[dictForAdd release];return NO;}else {NSLog(@"Add KeyChain Item Success!!!");[dictForAdd release];return YES;}}[dictForAdd release];return NO; }上面代碼中,首先構(gòu)建一個(gè)要添加到KeyChain中數(shù)據(jù)的Dictionary,包含一些基本的KeyChain Item的數(shù)據(jù)類(lèi)型,描述,訪問(wèn)分組以及最重要的數(shù)據(jù)等信息,最后通過(guò)調(diào)用SecItemAdd方法將我們需要保存的UUID保存到KeyChain中。
獲取KeyChain中相應(yīng)數(shù)據(jù)的代碼如下:
+ (NSString*)getUDIDFromKeyChain {NSMutableDictionary *dictForQuery = [[NSMutableDictionary alloc] init];[dictForQuery setValue:(id)kSecClassGenericPassword forKey:(id)kSecClass];// set Attr Description for query [dictForQuery setValue:[NSString stringWithUTF8String:kKeychainUDIDItemIdentifier]forKey:kSecAttrDescription];// set Attr Identity for queryNSData *keychainItemID = [NSData dataWithBytes:kKeychainUDIDItemIdentifierlength:strlen(kKeychainUDIDItemIdentifier)];[dictForQuery setObject:keychainItemID forKey:(id)kSecAttrGeneric];// The keychain access group attribute determines if this item can be shared// amongst multiple apps whose code signing entitlements contain the same keychain access group.NSString *accessGroup = [NSString stringWithUTF8String:kKeyChainUDIDAccessGroup];if (accessGroup != nil){ #if TARGET_IPHONE_SIMULATOR// Ignore the access group if running on the iPhone simulator.//// Apps that are built for the simulator aren't signed, so there's no keychain access group// for the simulator to check. This means that all apps can see all keychain items when run// on the simulator.//// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the// simulator will return -25243 (errSecNoAccessForItem). #else[dictForQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup]; #endif}[dictForQuery setValue:(id)kCFBooleanTrue forKey:(id)kSecMatchCaseInsensitive];[dictForQuery setValue:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];[dictForQuery setValue:(id)kCFBooleanTrue forKey:(id)kSecReturnData];OSStatus queryErr = noErr;NSData *udidValue = nil;NSString *udid = nil;queryErr = SecItemCopyMatching((CFDictionaryRef)dictForQuery, (CFTypeRef*)&udidValue);NSMutableDictionary *dict = nil;[dictForQuery setValue:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];queryErr = SecItemCopyMatching((CFDictionaryRef)dictForQuery, (CFTypeRef*)&dict);if (queryErr == errSecItemNotFound) {NSLog(@"KeyChain Item: %@ not found!!!", [NSString stringWithUTF8String:kKeychainUDIDItemIdentifier]);}else if (queryErr != errSecSuccess) {NSLog(@"KeyChain Item query Error!!! Error code:%ld", queryErr);}if (queryErr == errSecSuccess) {NSLog(@"KeyChain Item: %@", udidValue);if (udidValue) {udid = [NSString stringWithUTF8String:udidValue.bytes];}}[dictForQuery release];return udid; }上面代碼的流程也差不多一樣,首先創(chuàng)建一個(gè)Dictionary,其中設(shè)置一下查找條件,然后通過(guò)SecItemCopyMatching方法獲取到我們之前保存到KeyChain中的數(shù)據(jù)。
四、總結(jié)
本文介紹了使用KeyChain實(shí)現(xiàn)APP刪除后依然可以獲取到相同的UDID信息的解決方法。
你可能有疑問(wèn),如果系統(tǒng)升級(jí)以后,是否仍然可以獲取到之前記錄的UDID數(shù)據(jù)?
答案是肯定的,這一點(diǎn)我專(zhuān)門(mén)做了測(cè)試。就算我們程序刪除掉,系統(tǒng)經(jīng)過(guò)升級(jí)以后再安裝回來(lái),依舊可以獲取到與之前一致的UDID。但是當(dāng)我們把整個(gè)系統(tǒng)還原以后是否還能獲取到之前記錄的UDID,這一點(diǎn)我覺(jué)得應(yīng)該不行,不過(guò)手機(jī)里面數(shù)據(jù)太多,沒(méi)有測(cè)試,如果大家有興趣可以測(cè)試一下,驗(yàn)證一下我的猜想。
完整代碼地址:?https://github.com/smileEvday/SvUDID
大家如果要在真機(jī)運(yùn)行時(shí),需要替換兩個(gè)地方:
第一個(gè)地方是plist文件中的accessGroup中的APPIdentifier。
第二個(gè)地方是SvUDIDTools.m中的kKeyChainUDIDAccessGroup的APPIdentity為你所使用的profile的APPIdentifier。
文章和代碼中如果有什么不對(duì)的地方,歡迎指正,在這兒先謝過(guò)了。
?轉(zhuǎn)載自:http://www.cnblogs.com/smileEvday/p/UDID.html
總結(jié)
以上是生活随笔為你收集整理的ios使用KeyChain获取唯一不变的udid的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 史上最坑的证书报错解决方法:Code=3
- 下一篇: IOS使用MessageUI Frame