一、需求
多个小游戏app获取相同的设备唯一标识。
二、分析
iOS设备有很多设备标识,如:UDID、IDFA、IDFV、OpenIDFA、SimulateIDFA等。但是在iOS 7.0后很多标识就不能用了。多个app想要获取相同的设备唯一标识可以用到IDFV,然后再用KeyChain共享写入就能保证设备唯一标识相同了。
三、实现
3.1 确保多个app的BundleID前缀相同。
iOS6.x是通过BundleID前两部分来匹配,iOS7.x是通过除了最后一个部分来匹配。如果相同就是同一个Vender,共享同一个idfv的值。
Bundle ID | iOS 6.x | iOS 7.x+ |
---|---|---|
com.example.app1 | com.example.app1 | com.example.app1 |
com.example.app2 | com.example.app2 | com.example.app2 |
com.example.app.app1 | com.example.app.app1 | com.example.app.app1 |
com.example.app.app2 | com.example.app.app2 | com.example.app.app2 |
example | example | example |
3.2 多个app在同一开发者账号下
使用KeyChain来共享存储时,需要在相同的账号下,因为存储BundleID的时候默认在前面加上了账号的证书id。
3.3 在项目的 Build Phases
中添加Security.framework
库
3.4 在项目的 Signing & Capabilities
中添加Keychain Sharing
,加入Keychain Groups
分组。每个app都设置相同的Keychain Groups
值
3.5 导入文件 KeyChainTool.h/m
到项目中
//
// customKeyChainTool.h
//
#import <Foundation/Foundation.h>
@interface KeyChainTool : NSObject
+ (void)save:(NSString *)service data:(id)data;
+ (id)load:(NSString *)service;
+ (void)delete:(NSString *)service;
@end
//
// customKeyChainTool.m
//
#import "KeyChainTool.h"
#import <Security/Security.h>
@implementation KeyChainTool
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
//这个就和keychainitemwrapper里面提前预设了参数一样
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)delete:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((CFDictionaryRef)keychainQuery);
}
@end
3.6 添加获取设备唯一标识的UUID方法
+ (NSString *)getUUID {
NSString *strUUID = [(NSMutableDictionary*)[KeyChainTool load:@"com.guoke"] objectForKey:@"UUID"];
if (!strUUID || strUUID.length == 0) {
strUUID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
NSMutableDictionary *usernamepasswordKVPairs = [NSMutableDictionary dictionary];
[usernamepasswordKVPairs setObject:strUUID forKey:@"UUID"];
[KeyChainTool save:@"com.guoke" data:usernamepasswordKVPairs];
}
return strUUID;
}
四、测试
同账号打二个app包
app1 : com.guoke.test1
app2 : com.guoke.test2
测试流程 | 结果 |
---|---|
安装app1, 删除app1, 重装app1 | 二次安装获取到的strUUID相同 |
安装app1, 安装app2 | app1,app2获取到的strUUID相同 |
安装app1, 删除app1, 安装app2, | 二次安装获取到的strUUID相同 |
安装app1, 安装app2, 删除app1, 重装app1, | 三次安装获取到的strUUID相同 |
安装app1, 删除app1, 系统还原所有设置,重装app1 | 二次安装获取到的strUUID不同 |