UDID被弃用,使用UUID来作为设备的唯一标识。获取到UUID后,如果用NSUserDefaults存储,当程序被卸载后重装时,再获得的UUID和之前就不同了。使用keychain存储可以保证程序卸载重装时,UUID不变。但当刷机或者升级系统后,UUID还是会改变的。但这仍是目前为止最佳的解决办法了,如果有更好的解决办法,欢迎留言。
(我整理的解决办法的参考来源:http://blog.k-res.net/archives/1081.html)
给大家两个类:
UUID.h中的代码:
#import 尖括号(Foundation/Foundation.h) @interface UUID : NSObject +(NSString *)getUUID; @end UUID.m中的代码: #import "UUID.h" #import 尖括号(Foundation/Foundation.h) #import "KeychainItemWrapper.h" @implementation UUID +(NSString *)getUUID { KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:@"UUID" accessGroup:@"YOUR_BUNDLE_SEED.com.yourcompany.userinfo"]; NSString *strUUID = [keychainItem objectForKey:(id)CFBridgingRelease(kSecValueData)]; //首次执行该方法时,uuid为空 if ([strUUID isEqualToString:@""]) { CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault); strUUID = (NSString *)CFBridgingRelease(CFUUIDCreateString (kCFAllocatorDefault,uuidRef)); [keychainItem setObject:strUUID forKey:(id)CFBridgingRelease(kSecValueData)]; } return strUUID; } @end 第二个类是苹果官方的一个Demo里封装的,Demo的下载地址:http://developer.apple.com/library/ios/#samplecode/GenericKeychain/Listings/Classes_KeychainItemWrapper_h.html#//apple_ref/doc/uid/DTS40007797-Classes_KeychainItemWrapper_h-DontLinkElementID_9 KeychainItemWrapper.h中的代码: #import 尖括号(UIKit/UIKit.h) @interface KeychainItemWrapper : NSObject { NSMutableDictionary *keychainItemData; NSMutableDictionary *genericPasswordQuery; } @property (nonatomic, retain) NSMutableDictionary *keychainItemData; @property (nonatomic, retain) NSMutableDictionary *genericPasswordQuery; - (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup; - (void)setObject:(id)inObject forKey:(id)key; - (id)objectForKey:(id)key; - (void)resetKeychainItem; @end KeychainItemWrapper.m中的代码: #import "KeychainItemWrapper.h" #import 尖括号(Security/Security.h) @interface KeychainItemWrapper (PrivateMethods) - (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert; - (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert; - (void)writeToKeychain; @end @implementation KeychainItemWrapper @synthesize keychainItemData, genericPasswordQuery; - (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup; { if (self = [super init]) { genericPasswordQuery = [[NSMutableDictionary alloc] init]; [genericPasswordQuery setObject:(id)CFBridgingRelease(kSecClassGenericPassword) forKey:(id)kSecClass]; [genericPasswordQuery setObject:identifier forKey:(id)CFBridgingRelease(kSecAttrGeneric)]; if (accessGroup != nil) { #if TARGET_IPHONE_SIMULATOR #else [genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup]; #endif } [genericPasswordQuery setObject:(id)CFBridgingRelease(kSecMatchLimitOne) forKey:(id)kSecMatchLimit]; [genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes]; NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery]; NSMutableDictionary *outDictionary = nil; if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr) { [self resetKeychainItem]; [keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric]; if (accessGroup != nil) { #if TARGET_IPHONE_SIMULATOR #else [keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup]; #endif } } else { self.keychainItemData = [self secItemFormatToDictionary:outDictionary]; } [outDictionary release]; } return self; } - (void)dealloc { [keychainItemData release]; [genericPasswordQuery release]; [super dealloc]; } - (void)setObject:(id)inObject forKey:(id)key { if (inObject == nil) return; id currentObject = [keychainItemData objectForKey:key]; if (![currentObject isEqual:inObject]) { [keychainItemData setObject:inObject forKey:key]; [self writeToKeychain]; } } - (id)objectForKey:(id)key { return [keychainItemData objectForKey:key]; } - (void)resetKeychainItem { OSStatus junk = noErr; if (!keychainItemData) { self.keychainItemData = [[NSMutableDictionary alloc] init]; } else if (keychainItemData) { NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData]; junk = SecItemDelete((CFDictionaryRef)tempDictionary); NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." ); } [keychainItemData setObject:@"" forKey:(id)kSecAttrAccount]; [keychainItemData setObject:@"" forKey:(id)kSecAttrLabel]; [keychainItemData setObject:@"" forKey:(id)kSecAttrDescription]; [keychainItemData setObject:@"" forKey:(id)kSecValueData]; } - (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert { NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert]; [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; NSString *passwordString = [dictionaryToConvert objectForKey:(id)kSecValueData]; [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData]; return returnDictionary; } - (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert { NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert]; [returnDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; [returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; NSData *passwordData = NULL; if (SecItemCopyMatching((CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr) { [returnDictionary removeObjectForKey:(id)kSecReturnData]; NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length] encoding:NSUTF8StringEncoding] autorelease]; [returnDictionary setObject:password forKey:(id)kSecValueData]; } else { NSAssert(NO, @"Serious error, no matching item found in the keychain.\n"); } [passwordData release]; return returnDictionary; } - (void)writeToKeychain { NSDictionary *attributes = NULL; NSMutableDictionary *updateItem = NULL; OSStatus result; if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr) { updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes]; [updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(id)kSecClass]; NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData]; [tempCheck removeObjectForKey:(id)kSecClass]; #if TARGET_IPHONE_SIMULATOR [tempCheck removeObjectForKey:(id)kSecAttrAccessGroup]; #endif result = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck); NSAssert( result == noErr, @"Couldn't update the Keychain Item." ); } else { result = SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL); NSAssert( result == noErr, @"Couldn't add the Keychain Item." ); } } @end
PS: 1. KeychainItemWrapper类在官方Demo里面也有,如果我复制出错了,大家可以在上面的链接上下载官方Demo.
2.使用时要添加Security.framework
3. 尤为注意的是,UUID类下面这句代码中group的设置方法。
KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:@"UUID" accessGroup:@"YOUR_BUNDLE_SEED.com.yourcompany.userinfo"];
(1)在项目相同的目录下创建KeychainAccessGroups.plist文件。
(绝对有用)iOS获取UUID,并使用keychain存储
该文件的结构中最顶层的节点必须是一个名为“keychain-access-groups”的Array,并且该Array中每一项都是一个描述分组的NSString。YOUR_BUNDLE_SEED.com.yourcompany.userinfo就是要设置的组名。
(2)在项目相同的目录下创建KeychainAccessGroups.plist文件。在Target-Build Settings-Code Signing栏下的Code Signing Entitlements右侧添加KeychainAccessGroups.plist,如下图。
(绝对有用)iOS获取UUID,并使用keychain存储
到此,工作就完成了。
首次安装程序时,打印出一个uuid,当把程序卸载后,再用getUUID获得
uuid,打印出来的结果和之前相同。证明达到目的。
测试代码:
NSString * uuid= [UUID getUUID]; NSLog(@"uuid=%@",uuid);
测试结果:
uuid=19AAB430-9CB8-4325-ACC5-D7D386B68960