最近项目中需要获取到设备的唯一标志传送给后台存储备用,在UDID UUID IDFA等都存在诸多问题(什么问题可以自己查阅资料)的情况下,选择了 IDFV+keychain(当然特殊情况下也存在些许问题,但基本无影响,非常够用)。
identifierForVendor是apple给供应商唯一的一个值,也就是说同一个公司发行的的app在相同的设备上运行的时候会有这个相同的标识符。然而,如果用户删除了这个供应商的所有app然后再重新安装的话,这个标识符就会不一致。所以要结合keychain使用,在第一次使用App的时候把 IDFV存到keychain中,以后即使APP写在重装,我们只需要从keychain中取跟我们APP相关联的IDFV值就可以了,这样既保证了唯一性有保证了持久性。
废话不多说,下面直接来说怎么实现!
IDFV的获取方法:
NSString *identifierForVendor = [[UIDevice currentDevice].identifierForVendor UUIDString];
我们现在把IDFV+keychain封装成一个工具类:
新建一个继承自NSObject的类 IDFVTools 用来封装我们的IDFV+keychain
类中导入 #import <UIKit/UIKit.h> 不然调用 UIDevice 会报错
在IDFVTools.h中
+ (void)save:(NSString *)service data:(id)data;
+ (id)load:(NSString *)service;
+ (void)delete:(NSString *)service;
+ (NSString *)getIDFV;
在IDFVTools.m中
+ (NSString *)getIDFV
{
//定义存入keychain中的账号 一个标识 表示是某个app存储的内容 bundle id最好
NSString * const KEY_USERNAME = @"com.wdw.zzzz.username";
NSString * const KEY_PASSWORD = @"com.wdw.zzzz.password";
//测试用 清除keychain中的内容
//[IDFVTools delete:KEY_USERNAME_PASSWORD];
//读取账号中保存的内容
NSMutableDictionary *readUserDataDic = (NSMutableDictionary *)[IDFVTools load:KEY_USERNAME];
//NSLog(@"keychain==%@",readUserDataDic);
if (!readUserDataDic)
{//如果是第一次 肯定获取不到 这个时候就存储一个
NSString *deviceIdStr = [[[UIDevice currentDevice] identifierForVendor] UUIDString];//获取IDFV
//NSLog(@"identifierStr==%@",identifierStr);
NSMutableDictionary *needSaveDataDic = [NSMutableDictionary dictionaryWithObject:deviceIdStr forKey:KEY_PASSWORD];
//进行存储 并返回这个数据
[IDFVTools save:KEY_USERNAME data:needSaveDataDic];
return deviceIdStr;
}
else{return [readUserDataDic objectForKey:KEY_PASSWORD];}
}
//储存
+ (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];
//Configure the search setting
//Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue
[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)delete:(NSString *)service
{
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
}
哪里需要调用只需要
[IDFVTools getIDFV];
彩蛋!
除了上述方式还有一个操作钥匙串的好用的第三方,就是SAMKeychain!
如果你的项目用了cocopods 那么可以直接下载 具体可以看这里:
如果你的项目第三方框架都是手动导入 不喜欢用cocopods 可以直接参考gitHub:
调用的时候我们可以封装成一个方法:
+ (NSString *)getUniqueDevice
{
NSString *appName=[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey];
NSString *strApplicationUUID = [SAMKeychain passwordForService:appName account:account];
if (strApplicationUUID == nil)
{
strApplicationUUID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
NSError *error = nil;
SAMKeychainQuery *query = [[SAMKeychainQuery alloc] init];
query.service = appName;
query.account = account;
query.password = strApplicationUUID;
query.synchronizationMode = SAMKeychainQuerySynchronizationModeNo;
[query save:&error];
}
return strApplicationUUID;
}