1.需求
有时候我们需要标识设备的唯一性,区别设备来实现某些功能或者指定特定的设备,此时设备的唯一标识就派上用场了.
2.遇到的坑
对于上述需求,网上有N多方案,但是大部分都是已经过时的.比较好的也就那么几篇,例如这篇 ,我不想赘言.我简单描述下我使用过程中选择了又抛弃了的两个方案:
广告标示符advertisingIdentifier
这是iOS 6的方法,advertisingIdentifier是框架AdSupport.framework的一部分。ASIdentifierManager单例提供了一个方法advertisingIdentifier,通过调用该方法会返回一个NSUUID实例:
NSString *identifier = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
广告标示符是由系统存储着的。不过即使这是由系统存储的,但是有几种情况下,会重新生成广告标示符。如果用户完全重置系统((设置-> 通用 -> 还原 -> 还原位置与隐私) ,这个广告标示符会重新生成。另外如果用户明确的还原广告(设置程序->通用 -> 关于本机 -> 广告 -> 还原广告标示符),那么广告标示符也会重新生成。关于广告标示符的还原,有一点需要注意:如果程序在后台运行,此时用户“还原广告标示符”,然后再回到程序中,此时获取广告标示符并不会立即获得还原后的标示符。必须要终止程序,然后再重新启动程序,才能获得还原后的广告标示符。
就算用户不去设置这些东西,有的iPhone广告标识全为0,据说是苹果的一个BUG,至今不知如何解决,所以果断弃掉该方案.
Vindor标示符 identifierForVendor
这个标识和之前那个比较相似,都是苹果推荐使用的唯一标识,获取的方法是:
NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
这个id其实是和你的app的CFBundleIdentifier相关联的,不论你是把所有CFBundleIdentifier
的app全部卸载还是修改CFBundleIdentifier,他都会改变,特别是卸载,我们怎么可能保证用户不会去卸载你的app呢,所以直接弃掉.
3.解决方案
最后我直接使用第三方的openUDID,但是iOS7之后对剪切板的权限进行了限定,openUDID利用剪切板的方法并不那么灵了,所以我改进了一下,使用keychain来保存openUDID得到的值;
直接上代码:
+ (NSString *)getIdentifierForIPhone
{
NSString *identifier;
//取
NSMutableDictionary *readUserPwd = (NSMutableDictionary *)[XFTools load:KEY_USERNAME_PASSWORD];
if (readUserPwd) {
identifier = [readUserPwd objectForKey:KEY_PASSWORD];
}else{
identifier = [OpenUDID value];
NSMutableDictionary *usernamepasswordKVPairs = [NSMutableDictionary dictionary];
[usernamepasswordKVPairs setObject:identifier forKey:KEY_PASSWORD];
//存
[Tools save:KEY_USERNAME_PASSWORD data:usernamepasswordKVPairs];
}
return identifier;
}
//存
+ (void)save:(NSString *)service data:(id)data {
//Get search dictionary
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Delete old item before add new item
SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
//Add new object to search dictionary(Attention:the data format)
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];
//Add item to keychain with the search dictionary
SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL);
}
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kSecClassGenericPassword,(__bridge id)kSecClass,
service, (__bridge id)kSecAttrService,
service, (__bridge id)kSecAttrAccount,
(__bridge id)kSecAttrAccessibleAfterFirstUnlock,(__bridge id)kSecAttrAccessible,
nil];
}
//取
+ (id)load:(NSString *)service {
id ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
[keychainQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[keychainQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
@try {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
} @catch (NSException *e) {
NSLog(@"Unarchive of %@ failed: %@", service, e);
} @finally {
}
}
if (keyData)
CFRelease(keyData);
return ret;
}
//删
+ (void)deletekeychain:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
}
如果觉得繁琐,可以使用第三方的keychain的操作类库,到目前为止钥匙串保存的信息还是比较值得信赖的.