iOS的UDID廢用以及UUID配合keychain的替換方案實現

520ss發表於2013-09-22

首先,簡單介紹一下UDID這個東西:

UDIDUnique Device Identifier的簡稱,也就是唯一裝置標識的意思。於iOS SDK中取得的方法是UIDevice的一個叫uniqueIdentifier的NSString*,由於這個ID字串是基於裝置的,應用開發人員可以通過獲取此ID來用於記錄區分裝置。正是由於這個特性,可能會導致一些隱私等等相關的問題,Apple於iOS5中將這個UDID廢掉了,SDK中被標記為了Deprecated,雖然為了相容低版本的原始碼而繼續存在,但並不會再返回任何有實際意義的東西。

最近在做Flurry的統計功能時,發現還是需要用到可以識別裝置的東西的,好方便分析資料,經過一段時間的研究、試驗,發現了這個應該還算是比較靠譜的方法……

其實早在UDID被deprecated的訊息剛出來時,就已經有很多人開始研究對策了,我也google到了各種五花八門的解決方案,最後還是覺得這個UUID的方案比較合適,畢竟是蘋果官方文件裡推薦的替換UDID的方法。

關於UUID的具體說明可以檢視下面參考文章中給出的蘋果官方文件連結。簡單來說,UUID就是一個隨機序列字串生成器,有點像Microsoft Windows的COM GUID生成器的作用,比起自己隨機一個字串的好處就是這東西能夠保證唯一性,適用於標記。呼叫方法如下:

1 CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
2 NSString *uuid = (NSString *)CFUUIDCreateString (kCFAllocatorDefault,uuidRef);

然後呢,官方建議的做法是用:

1 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
2 [userDefaults setObject:uuid forKey:@"UUID"];

這樣的做法把生成的ID儲存起來,下次再用的時候就直接讀取已經儲存的ID了。顯然,那個UUID生成只是個“隨機字串”生成器,並不能像UDID那樣保證每次取得的串都一樣!儲存起來雖然能保證使用者再次開啟這個應用時,能夠獲得一致的標識ID,但不能保證使用者刪除應用重新安裝後這個ID的一致性,因為NSUserDefaults只是個像遊戲存檔一樣的東西,遊戲刪了,存檔也就跟著一塊刪了。所以,這個“存存檔”的方法並不是一個比較完善的解決方案,一個更好的做法是利用keychain儲存這個生成的UUID。

關於keychain這個東西的概念可以到這裡學習:https://developer.apple.com/library/ios/#documentation/Security/Conceptual/keychainServConcepts/01introduction/introduction.html,簡言之就是每個應用程式都有一個可以用於安全儲存一些如密碼、認證等資訊的keychain,通過對應用簽名時的一些設定,還可以利用keychain的方式實現同一開發者簽證(就是相同bundle seed)下的不同應用之間共享資訊的操作。比如你有一個開發者帳戶,並開發了兩個不同的應用A和B,然後通過對A和B的keychain access group這個東西指定共用的訪問分組,就可以實現共享此keychain中的內容。而且,對比NSUserDefaults的一點不同之處就是此資訊不會隨應用的刪除而消失!

關於Keychain的應用,Apple提供了一個叫GenericKeychain的例子程式,在這裡:http://developer.apple.com/library/ios/#samplecode/GenericKeychain/Listings/Classes_KeychainItemWrapper_h.html#//apple_ref/doc/uid/DTS40007797-Classes_KeychainItemWrapper_h-DontLinkElementID_9,其中封裝了一個簡化Keychain操作的類:KeychainItemWrapper,可以拿來直接使用,記得加入Security.framework!

參考程式碼如下:

1 KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc]
2 initWithIdentifier:@"UUID"
3 accessGroup:@"YOUR_BUNDLE_SEED.com.yourcompany.userinfo"];
4 NSString *strUUID = [keychainItem objectForKey:(id)kSecValueData];
5 if ([strUUID isEqualToString:@""])
6 {
7 CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
8 strUUID = (NSString *)CFUUIDCreateString (kCFAllocatorDefault,uuidRef);
9 [keychainItem setObject:strUUID forKey:(id)kSecValueData];
10 }
11 [FlurryAnalytics setUserID:strUUID];
12 [keychainItem release];

關於keychain access groups的設定,傳統方法是在Xcode專案target的Build Settings的Code Signing段中加入Code Signing Entitlements的配置檔案,加入group資訊,詳細操作搜尋一下就能找到。新版本的Xcode直接整合了生成Entitlements的功能,在指定target的Summary的最後一個段Entitlements中勾選Enable Entitlements,然後在下面的Keychain Access Groups中加入”com.yourcompany.userinfo”類的共享group名。這裡要注意一點,參考IDE預設生成的對應app自身id名的group可以發現,這裡的group並沒有加入bundle seed,看一下生成的entitlements檔案中的內容可以發現group名頭部被自動新增了“$(AppIdentifierPrefix)”這種替換變數!
在最開始測試Keychain讀寫時,參考下面”如何解決蘋果公司禁止用UUID的方法”這篇文章中的方法,一直在keychainItem setObject時報異常,斷點跟蹤了一下,發現是封裝類的writeToKeychain函式中的SecItemAdd函式返回
errSecParam                  = -50,     /* One or more parameters passed to a function where not valid. */
這個錯誤,最初以為是access group設定的問題,替換引數為nil後報錯依舊.最後google到stack overflow上的”Storing keys in KeyChain with KeyChainItemWrapper”這篇問答後意識到原來setObject的key不能隨意指定任意的string,而必須使用預定義好的一些key,替換key為kSecValueData後,問題解決。

參考文章:

Deprecated UIDevice Methodshttps://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIDevice_Class/DeprecationAppendix/AppendixADeprecatedAPI.html#//apple_ref/occ/instp/UIDevice/uniqueIdentifier

CFUUIDhttps://developer.apple.com/library/ios/#documentation/CoreFoundation/Reference/CFUUIDRef/Reference/reference.html#//apple_ref/c/func/CFUUIDCreate

如何解決蘋果公司禁止用UUID的方法。 http://blog.csdn.net/mengtnt/article/details/7410373

Storing keys in KeyChain with KeyChainItemWrapper http://stackoverflow.com/questions/7117885/storing-keys-in-keychain-with-keychainitemwrapper

相關文章