1. 程式人生 > >iOS7: 如何獲取不變的UDID

iOS7: 如何獲取不變的UDID

如何使用KeyChain儲存和獲取UDID

  本文是iOS7系列文章第一篇文章,主要介紹使用KeyChain儲存和獲取APP資料,解決iOS7上獲取不變UDID的問題。並給出一個獲取UDID的工具類,使用方便,只需要替換兩個地方即可。

一、iOS不用版本獲取UDID的方法比較

  1)iOS 5.0

  iOS 2.0版本以後UIDevice提供一個獲取裝置唯一識別符號的方法uniqueIdentifier,通過該方法我們可以獲取裝置的序列號,這個也是目前為止唯一可以確認唯一的標示符。好景不長,因為該唯一識別符號與手機一一對應,蘋果覺得可能會洩露使用者隱私,所以在 iOS 5.0之後該方法就被廢棄掉了。

  而且蘋果做的更狠,今年5月份以後提交App Store的產品都不允許再用uniqueIdentifier介面,甚至有些朋友因為程式碼中有UDID還被打回來,看來這條路是被封死了。

  2)iOS 6.0

  iOS 6.0系統新增了兩個用於替換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.

  大概意思就是“同一開發商的APP在指定機器上都會獲得同一個ID。當我們刪除了某一個裝置上某個開發商的所有APP之後,下次獲取將會獲取到不同的ID。” 也就是說我們通過該介面不能獲取用來唯一標識裝置的ID,問題總是難不倒聰明的程式設計師,於是大家想到了使用WiFi的mac地址來取代已經廢棄了的uniqueIdentifier方法。具體的方法晚上有很多,大家感興趣的可以自己找找,這兒提供一個網址: http://stackoverflow.com/questions/677530/how-can-i-programmatically-get-the-mac-address-of-an-iphone

  3)iOS 7.0  

  iOS 7中蘋果再一次無情的封殺mac地址,使用之前的方法獲取到的mac地址全部都變成了02:00:00:00:00:00。有問題總的解決啊,於是四處查資料,終於有了思路是否可以使用KeyChain來儲存獲取到的唯一標示符呢,這樣以後即使APP刪了再裝回來,也可以從KeyChain中讀取回來。有了方向以後就開始做,看關於KeyChain的官方文件,看官方使用KeyChain的Demo,大概花了一下午時間,問題終於解決了。 

二、KeyChain介紹

   我們搞iOS開發,一定都知道OS X裡面的KeyChain(鑰匙串),通常要鄉鎮及除錯的話,都得安裝證書之類的,這些證書就是儲存在KeyChain中,還有我們平時瀏覽網頁記錄的賬號密碼也都是記錄在KeyChain中。iOS中的KeyChain相比OS X比較簡單,整個系統只有一個KeyChain,每個程式都可以往KeyChain中記錄資料,而且只能讀取到自己程式記錄在KeyChain中的資料。iOS中Security.framework框架提供了四個主要的方法來操作KeyChain:

// 查詢
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)

  這四個方法引數比較複雜,一旦傳錯就會導致操作KeyChain失敗,這塊兒文件中介紹的比較詳細,大家可以查查官方文件Keychain Services Reference

  前面提到了每個APP只允許訪問自己在KeyChain中記錄的資料,那麼是不是就沒有別的辦法訪問其他APP存在KeyChain的資料了?

  蘋果提供了一個方法允許同一個發商的多個APP訪問各APP之間的途徑,即在調SecItemAdd新增資料的時候指定AccessGroup,即訪問組。一個APP可以屬於同事屬於多個分組,新增KeyChain資料訪問組需要做一下兩件事情:

  a、在APP target的bulibSetting裡面設定Code Signing Entitlements,指向包含AceessGroup的分組資訊的plist檔案。該檔案必須和工程檔案在同一個目錄下,我在新增訪問分組的時候就因為plist檔案位置問題,操作KeyChain失敗,查詢這個問題還花了好久的時間。

  b、在工程目錄下新建一個KeychainAccessGroups.plist檔案,該檔案的結構中最頂層的節點必須是一個名為“keychain-access-groups”的Array,並且該Array中每一項都是一個描述分組的NSString。對於String的格式也有相應要求,格式為:"AppIdentifier.com.***",其中APPIdentifier就是你的開發者帳號對應的ID。

  c、在程式碼中往KeyChain中Add資料的時候,設定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中直接拷貝過來的,根據註釋我們可以看到,模擬器是不支援AccessGroup的,所以才行了預編譯巨集來選擇性新增。

  注:appIdentifer就是開發者帳號的那一串標識,如下圖所示:

   

  開啟xcode的Organizer,選擇Device選項卡,連線裝置就可以看到裝置上安裝的開發者賬號描述檔案列表,其中第五列最開始的10個字元即為App Identifier,這塊兒前面寫的不是很清楚,好多朋友加我qq問我,今天特地補上。

三、使用KeyChain儲存和獲取UDID

  說了這麼多終於進入正題了,如何在iOS 7上面獲取到不變的UDID。我們將第二部分所講的知識直接應用進來就可以了輕鬆達到我們要的效果了,下面我們先看看往如何將獲取到的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 keychain
        writeErr = 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;
}

  上面程式碼中,首先構建一個要新增到KeyChain中資料的Dictionary,包含一些基本的KeyChain Item的資料型別,描述,訪問分組以及最重要的資料等資訊,最後通過呼叫SecItemAdd方法將我們需要儲存的UUID儲存到KeyChain中。

  獲取KeyChain中相應資料的程式碼如下:

+ (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 query
    NSData *keychainItemID = [NSData dataWithBytes:kKeychainUDIDItemIdentifier
                                            length: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;
}

  上面程式碼的流程也差不多一樣,首先建立一個Dictionary,其中設定一下查詢條件,然後通過SecItemCopyMatching方法獲取到我們之前儲存到KeyChain中的資料。

四、總結

  本文介紹了使用KeyChain實現APP刪除後依然可以獲取到相同的UDID資訊的解決方法。

  你可能有疑問,如果系統升級以後,是否仍然可以獲取到之前記錄的UDID資料?

  答案是肯定的,這一點我專門做了測試。就算我們程式刪除掉,系統經過升級以後再安裝回來,依舊可以獲取到與之前一致的UDID。但是當我們把整個系統還原以後是否還能獲取到之前記錄的UDID,這一點我覺得應該不行,不過手機裡面資料太多,沒有測試,如果大家有興趣可以測試一下,驗證一下我的猜想。

  大家如果要在真機執行時,需要替換兩個地方:

  第一個地方是plist檔案中的accessGroup中的APPIdentifier。

  第二個地方是SvUDIDTools.m中的kKeyChainUDIDAccessGroup的APPIdentity為你所使用的profile的APPIdentifier。

  文章和程式碼中如果有什麼不對的地方,歡迎指正,在這兒先謝過了。

注:如果覺得本文幫到了你,別忘了點推薦

  轉載請著名出處,有什麼問題歡迎留言

  歡迎一起討論iOS開發的知識!!!

相關推薦

iOS7: 如何獲取UDID

如何使用KeyChain儲存和獲取UDID   本文是iOS7系列文章第一篇文章,主要介紹使用KeyChain儲存和獲取APP資料,解決iOS7上獲取不變UDID的問題。並給出一個獲取UDID的工具類,使用方便,只需要替換兩個地方即可。 一、iOS不用版本獲取UDID的方法比較   1)iO

iOS: 獲取UDID

ref 才會 baidu 成了 個人 strip targe lock 添加 iOS: 獲取不變的UDID iOS唯一標識的歷史歷程 iOS 6.0 在iOS6.0以前,是使用uniqueIdentifier來獲取手機的唯一標識,後來蘋果感覺這樣會泄露用戶隱藏,就封掉了這

如何獲取UDID

一、iOS不用版本獲取UDID的方法比較   1)iOS 5.0   iOS 2.0版本以後UIDevice提供一個獲取裝置唯一識別符號的方法uniqueIdentifier,通過該方法我們可以獲取裝置的序列號,這個也是目前為止唯一可以確認唯一的標示符。好景不長,因

Webview獲取連續H5頁面的title,並解決回退時title的問題

在應用中,有的頁面會使用h5來實現,而title確是客戶端來實現的,正確的獲取h5的標題,設定正確的title 是必須的。 這個在webview中通過api很容易實現。只需要呼叫這個方法就可以了。 mWebView.setWebChromeClient(new We

iOS7獲取裝置UDID、IMEI、ICCID、序列號、Mac地址等資訊

在iOS7之前, 可以方便的使用 [[UIDevice currentDevice] uniqueIdentifier] 來獲取裝置的UDID,但是在iOS7之後這個方法不再適用。 你可以用[[UIDevicecurrentDevice]valueForKey:@"un

gbMzddN解決SpriNgMc獲取到put方

href 獲取 blank gbm tar htm .com auto ddn gbMzddN解決SpriNgMc獲取不到put方 深蓬 gbMzddN解決SpriNgMc獲取不到put方gbMzddN解決SpriNgMc獲取不到put方

【學習筆記】SIFT尺度特征 (配合UCF-CRCV課程視頻)

rri cnblogs -o mask 畫出 blocks http ucf 產生 SIFT尺度不變特征 D. Lowe. Distinctive image features from scale-invariant key points, IJCV 2004 -Lect

String作為輸出型參數時獲取到值

聲明 調用 情況 過程 執行 變化 錯誤 更新 取不到值 有時候在一個方法中,我們需要返回多個字符串,而又不想將這些字段包成一個類。此時就需要使用輸出型參數。 但是如果將輸出型參數的類型聲明為String,那麽調用該方法後,是獲取不到我們想要的值的。 測試代碼如下:

js獲取帶單位的像素值

replace button onclick num parseint 數值 調用 寬度 字體   所謂獲取不帶單位的像素值就是獲取比如元素的寬度、高度、字體大小、外邊距、內邊距等值但是去掉像素單位。   比如:某一個元素的寬度是100px,現在我要獲取這個這個值但是不帶

js獲取freemarker量的值

test type doc servle ret method read 需要 mod 後臺數據 @RequestMapping(value="/suit_item", method = RequestMethod.GET) public String getSu

路由器動態ip獲取

路由器 動態ip 一直獲取不到 路由器中設置動態IP上網後,路由器中一直顯示正在獲取動態IP,但就是獲取不到動態IP地址信息。出現這樣的情況,原因是多種多樣的,建議大家按照下面的順序排查 1、寬帶問題 2、路由器連接問題 3、IP地址沖突 4、網線問題 路由器動態ip獲取不到 一、寬帶問題 首

路由器重啟,是否ip就永遠

由器 class 可能 公網ip 不變 時間 自動 如果 重啟 今天發現公司的公網ip突然變了,沒有人去動過路由器怎麽會這樣呢?經查原因如下:1、不一定,IP變化是每一次撥號重新獲取的。2、路由器重啟了,會自動撥號,獲得IP3、但如果說因各種原因,掉線,路由器也會重新撥號鏈

SQLite Expert表分離和解決SQLite Expert刪除表後大小的問題

大小 nbsp where 效果 外鍵 mob 一點 冗余 java代碼 最後要使用到號碼歸屬地的查詢,在網上找到一個數據庫文件。大小有12M多,壓縮成zip也有1.9M,這樣對於一個apk的大小非常不利,後來看了一下數據庫的內容,發現有非常多冗余。特別是中文字

解決從json文件中獲取到數據的問題

app 一個數 你是 項目 clas control cor solid 例子    在寫項目時我們需要數據渲染,在渲染的過程中有時會發現有些數據一直渲染不到頁面上;  解決此問題我總結了一下幾點   1、首先先查找自己的json文件路徑是否正確,      錯誤示範

303. Range Sum Query - Immutable 數組範圍求和 -

family elements ger mon integer ack man gin 不變 Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j),

ajax post參數後臺獲取到的問題

png cnblogs content .html src .com tom attribute targe AJAX post傳參後臺獲取不到查詢參數。 網上找了各種方法,包括設置content-type,又是把json轉成json格式字符串,問題依然存在,但是把po

node進階| 解決表單enctype="multipart/form-data" 時獲取到Input值的問題

分割字符串 odi logs -1 字符串 var dex 路徑 date() 今天在學習node踩到一個坑:form設置enctype="multipart/form-data"上傳文件時,無法獲取到表單其他input的值。 因為之前上傳文件用的是 formidable

spring-boot上傳文件MultiPartFile獲取到文件問題解決

smu number o-c you output oar multi element cep 1.現象是在spring-boot裏加入commons-fileupload jar並且配置了mutilPart的bean,在upload的POST請求後,發現 multipa

劍指offer之 奇數偶數數組位置調整且保存順序

cor clas for [] 空間 ++ highlight n) == public class Solution { public void reOrderArray(int [] array) { reOrderCore(

array_filter 過濾一維中空數組,數組的序列

logs type arp true [1] ont cnblogs content 序列 <?php header(‘Content-type:text;charset=utf8‘); $str = "%11111%22222%333333%"; $arr =