IDFV
IDFV-identifierForVendor(Vendor 标示符),通过 [UIDevice currentDevice].identifierForVendor.UUIDString 来获取。是通过 bundleID 的反转的前两部分进行匹配,如果相同是同一个 Vendor ,例如对于 com.mayan.app_1 和 com.mayan.app_2 这两个 bundleID 来说,就属于同一个 Vendor ,共享同一个 IDFV,和 IDFA 不同的是,IDFV 的值一定能取到的,所以非常适合于作为内部用户行为分析的主 ID 来识别用户。但是用户删除了该 APP ,则 IDFV 值会被重置,再次安装此 APP ,IDFV 的值和之前的不同。
IDFV + keychain
通过以上几种储存唯一标识的方法的分析,总结一下各有优劣。很多方法被苹果禁止或者漏洞太多,越来越不被开发者使用,现在苹果主推 IDFA 和 IDFV 这两种方法,分别对外和对内,但是 IDFV 在 APP 重新安装时会更改,所以我的方法是通过第一次生成的 IDFV 存储到 keychain 中,以后每次获取标识符都从 keychain 中获取。
#import <UIKit/UIKit.h>
@interface MYVendorToll : NSObject
+ (NSString *)getIDFV;
@end
#import "MYVendorToll.h"
#import "MYKeyChainTool.h"
@implementation MYVendorToll
+ (NSString *)getIDFV
{
NSString *IDFV = (NSString *)[MYKeyChainTool load:@"IDFV"];
if ([IDFV isEqualToString:@""] || !IDFV) {
IDFV = [UIDevice currentDevice].identifierForVendor.UUIDString;
[MYKeyChainTool save:@"IDFV" data:IDFV];
}
return IDFV;
}
@end
#import <Foundation/Foundation.h>
@interface MYKeyChainTool : NSObject
+ (void)save:(NSString *)service data:(id)data;
+ (id)load:(NSString *)service;
+ (void)deleteKeyData:(NSString *)service;
@end
#import "MYKeyChainTool.h"
@implementation MYKeyChainTool
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(id)kSecClassGenericPassword,(id)kSecClass,
service, (id)kSecAttrService,
service, (id)kSecAttrAccount,
(id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
nil];
}
+ (void)save:(NSString *)service data:(id)data {
//Get search dictionary
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Delete old item before add new item
SecItemDelete((CFDictionaryRef)keychainQuery);
//Add new object to search dictionary(Attention:the data format)
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
//Add item to keychain with the search dictionary
SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}
+ (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:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
[keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((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)deleteKeyData:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((CFDictionaryRef)keychainQuery);
}
@end










