UDID、UUID+keychain

首先,簡單介紹一下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生成器的作用,比起自己隨機一個字符串的好處就是這東西能夠保證唯一性,適用於標記。調用方法如下:

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

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

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[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!

參考代碼如下:

KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc]
initWithIdentifier:@"UUID"
accessGroup:@"YOUR_BUNDLE_SEED.com.yourcompany.userinfo"];
NSString *strUUID = [keychainItem objectForKey:(id)kSecValueData];
if ([strUUID isEqualToString:@""])
{
CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
strUUID = (NSString *)CFUUIDCreateString (kCFAllocatorDefault,uuidRef);
[keychainItem setObject:strUUID forKey:(id)kSecValueData];
}
[FlurryAnalytics setUserID:strUUID];
[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 Methods https://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIDevice_Class/DeprecationAppendix/AppendixADeprecatedAPI.html#//apple_ref/occ/instp/UIDevice/uniqueIdentifier

CFUUID https://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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章