使用Keychain解决iOS的UDID问题
iOS 5之前可以通过uniqueIdentifier
来获取设备的UDID.由于隐私问题,Apple已经禁止使用该方法来获取UDID了.
“Do not use the
uniqueIdentifier
property. To create a unique identifier specific to your app, you can call the CFUUIDCreate
function to create a UUID, and write it to the defaults database using the NSUserDefaults
class.”
Apple建议使用CFUUIDCreate
方法来创建UUID,然后保存在NSUserDefaults
.
此外Apple还提供了两种方法来获取:
- IFA/IDFA (Identifier for Advertisers),局限就是应用必须是显示了广告,并且用户可以重置.
- IFV/IDFV (Identifier for Vendor): 应用删除了,UUID也会重置.
解决方案:
使用Keychain,将UUID当做密码信息来存储.
大致流程:
1, 通过AdSupport
获取UUID(原因AdSupport
可以跨应用)
+ (NSString *)appleIFA {
NSString *ifa = nil;
Class ASIdentifierManagerClass = NSClassFromString(@"ASIdentifierManager");
if (ASIdentifierManagerClass) { // a dynamic way of checking if AdSupport.framework is available
SEL sharedManagerSelector = NSSelectorFromString(@"sharedManager");
id sharedManager = ((id (*)(id, SEL))[ASIdentifierManagerClass methodForSelector:sharedManagerSelector])(ASIdentifierManagerClass, sharedManagerSelector);
SEL advertisingIdentifierSelector = NSSelectorFromString(@"advertisingIdentifier");
NSUUID *advertisingIdentifier = ((NSUUID* (*)(id, SEL))[sharedManager methodForSelector:advertisingIdentifierSelector])(sharedManager, advertisingIdentifierSelector);
ifa = [advertisingIdentifier UUIDString];
}
return ifa;
}
2, 如果不支持AdSupport
,那就使用IFV/IDFV (Identifier for Vendor)
+ (NSString *)appleIFV {
if(NSClassFromString(@"UIDevice") && [UIDevice instancesRespondToSelector:@selector(identifierForVendor)]) {
// only available in iOS >= 6.0
return [[UIDevice currentDevice].identifierForVendor UUIDString];
}
return nil;
}
3, 如果以上的都不支持,使用CFUUIDRef
手动创建UUID
+ (NSString *)randomUUID {
if(NSClassFromString(@"NSUUID")) { // only available in iOS >= 6.0
return [[NSUUID UUID] UUIDString];
}
CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
CFStringRef cfuuid = CFUUIDCreateString(kCFAllocatorDefault, uuidRef);
CFRelease(uuidRef);
NSString *uuid = [((__bridge NSString *) cfuuid) copy];
CFRelease(cfuuid);
return uuid;
}
最后,添加到Keychain
+ (void)setValue:(NSString *)value forKey:(NSString *)key inService:(NSString *)service {
NSMutableDictionary *keychainItem = [[NSMutableDictionary alloc] init];
keychainItem[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
keychainItem[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAlways;
keychainItem[(__bridge id)kSecAttrAccount] = key;
keychainItem[(__bridge id)kSecAttrService] = service;
keychainItem[(__bridge id)kSecValueData] = [value dataUsingEncoding:NSUTF8StringEncoding];
SecItemAdd((__bridge CFDictionaryRef)keychainItem, NULL);
}
如果更新了provisioning profile的话, Keychain data会丢失.所以我们应该将UUID在NSUserDefault
备份.
[[NSUserDefaults standardUserDefaults] setObject:@”123456-1234-1234-12345678” forKey:@"deviceUID"];
[[NSUserDefaults standardUserDefaults] synchronize];
99%参考Persistent, Cross-Install Device Identifier on iOS: Using Keychain:https://blog.onliquid.com/persistent-device-unique-identifier-ios-keychain
源码:https://gist.github.com/miguelcma/e8f291e54b025815ca46
源码有一个死循环bug,看留言可以发现问题