本文是iOS7系列文章第一篇文章,主要介绍使用KeyChain保存和获取APP数据,解决iOS7上获取不变UDID的问题。并给出一个获取UDID的工具类,使用方便,只需要替换两个地方即可。
一、iOS不用版本获取UDID的方法比较
1)iOS 5.0
iOS 2.0版本以后UIDevice提供一个获取设备唯一标识符的方法uniqueIdentifier,通过该方法我们可以获取设备的序列号,这个也是目前为止唯一可以确认唯一的标示符。好景不长,因为该唯一标识符与手机一一对应,苹果觉得可能会泄露用户隐私,所以在 iOS 5.0之后该方法就被废弃掉了。
而且苹果做的更狠,今年5月份以后提交App Store的产品都不允许再用uniqueIdentifier接口,甚至有些朋友因为代码中有UDID还被打回来,看来这条路是被封死了。
2)iOS 6.0
iOS 6.0系统新增了两个用于替换uniqueIdentifier的接口,分别是:identifierForVendor,advertisingIdentifier。
identifierForVendor接口的官方文档介绍如下:
The value of this property is the same for apps that come from the same vendor running on the same device. A different value is returned for apps on the same device that come from different vendors, and for apps on different devices regardless of vendor.
The value of this property may be nil if the app is running in the background, before the user has unlocked the device the first time after the device has been restarted. If the value is nil, wait and get the value again later.
The value in this property remains the same while the app (or another app from the same vendor) is installed on the iOS device. The value changes when the user deletes all of that vendor’s apps from the device and subsequently reinstalls one or more of them. Therefore, if your app stores the value of this property anywhere, you should gracefully handle situations where the identifier changes.
大概意思就是“同一开发商的APP在指定机器上都会获得同一个ID。当我们删除了某一个设备上某个开发商的所有APP之后,下次获取将会获取到不同的ID。” 也就是说我们通过该接口不能获取用来唯一标识设备的ID,问题总是难不倒聪明的程序员,于是大家想到了使用WiFi的mac地址来取代已经废弃了的uniqueIdentifier方法。具体的方法晚上有很多,大家感兴趣的可以自己找找,这儿提供一个网址: http://stackoverflow.com/questions/677530/how-can-i-programmatically-get-the-mac-address-of-an-iphone
3)iOS 7.0
iOS 7中苹果再一次无情的封杀mac地址,使用之前的方法获取到的mac地址全部都变成了02:00:00:00:00:00。有问题总的解决啊,于是四处查资料,终于有了思路是否可以使用KeyChain来保存获取到的唯一标示符呢,这样以后即使APP删了再装回来,也可以从KeyChain中读取回来。有了方向以后就开始做,看关于KeyChain的官方文档,看官方使用KeyChain的Demo,大概花了一下午时间,问题终于解决了。
二、KeyChain介绍
我们搞iOS开发,一定都知道OS X里面的KeyChain(钥匙串),通常要乡镇及调试的话,都得安装证书之类的,这些证书就是保存在KeyChain中,还有我们平时浏览网页记录的账号密码也都是记录在KeyChain中。iOS中的KeyChain相比OS X比较简单,整个系统只有一个KeyChain,每个程序都可以往KeyChain中记录数据,而且只能读取到自己程序记录在KeyChain中的数据。iOS中Security.framework框架提供了四个主要的方法来操作KeyChain:
// 查询
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result);
// 添加
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result);
// 更新KeyChain中的Item
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate);
// 删除KeyChain中的Item
OSStatus SecItemDelete(CFDictionaryRef query)
这四个方法参数比较复杂,一旦传错就会导致操作KeyChain失败,这块儿文档中介绍的比较详细,大家可以查查官方文档Keychain Services Reference。
前面提到了每个APP只允许访问自己在KeyChain中记录的数据,那么是不是就没有别的办法访问其他APP存在KeyChain的数据了?
苹果提供了一个方法允许同一个发商的多个APP访问各APP之间的途径,即在调SecItemAdd添加数据的时候指定AccessGroup,即访问组。一个APP可以属于同事属于多个分组,添加KeyChain数据访问组需要做一下两件事情:
a、在APP target的bulibSetting里面设置Code Signing Entitlements,指向包含AceessGroup的分组信息的plist文件。该文件必须和工程文件在同一个目录下,我在添加访问分组的时候就因为plist文件位置问题,操作KeyChain失败,查找这个问题还花了好久的时间。
b、在工程目录下新建一个KeychainAccessGroups.plist文件,该文件的结构中最顶层的节点必须是一个名为“keychain-access-groups”的Array,并且该Array中每一项都是一个描述分组的NSString。对于String的格式也有相应要求,格式为:"AppIdentifier.com.***",其中APPIdentifier就是你的开发者帐号对应的ID。
c、在代码中往KeyChain中Add数据的时候,设置kSecAttrAccessGroup,代码如下:
NSString *accessGroup = [NSString stringWithUTF8String:"APPIdentifier.com.cnblogs.smileEvday"];
if (accessGroup != nil)
{
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[dictForQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
}
这段代码是从官方的Demo中直接拷贝过来的,根据注释我们可以看到,模拟器是不支持AccessGroup的,所以才行了预编译宏来选择性添加。
注:appIdentifer就是开发者帐号的那一串标识,如下图所示:
打开xcode的Organizer,选择Device选项卡,连接设备就可以看到设备上安装的开发者账号描述文件列表,其中第五列最开始的10个字符即为App Identifier,这块儿前面写的不是很清楚,好多朋友加我qq问我,今天特地补上。
三、使用KeyChain保存和获取UDID
说了这么多终于进入正题了,如何在iOS 7上面获取到不变的UDID。我们将第二部分所讲的知识直接应用进来就可以了轻松达到我们要的效果了,下面我们先看看往如何将获取到的identifierForVendor添加到KeyChain中的代码。
+ (BOOL)settUDIDToKeyChain:(NSString*)udid
{
NSMutableDictionary *dictForAdd = [[NSMutableDictionary alloc] init];
[dictForAdd setValue:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[dictForAdd setValue:[NSString stringWithUTF8String:kKeychainUDIDItemIdentifier] forKey:kSecAttrDescription];
[dictForAdd setValue:@"UUID" forKey:(id)kSecAttrGeneric];
// Default attributes for keychain item.
[dictForAdd setObject:@"" forKey:(id)kSecAttrAccount];
[dictForAdd setObject:@"" forKey:(id)kSecAttrLabel];
// The keychain access group attribute determines if this item can be shared
// amongst multiple apps whose code signing entitlements contain the same keychain access group.
NSString *accessGroup = [NSString stringWithUTF8String:kKeyChainUDIDAccessGroup];
if (accessGroup != nil)
{
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[dictForAdd setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
}
const char *udidStr = [udid UTF8String];
NSData *keyChainItemValue = [NSData dataWithBytes:udidStr length:strlen(udidStr)];
[dictForAdd setValue:keyChainItemValue forKey:(id)kSecValueData];
OSStatus writeErr = noErr;
if ([SvUDIDTools getUDIDFromKeyChain]) { // there is item in keychain
[SvUDIDTools updateUDIDInKeyChain:udid];
[dictForAdd release];
return YES;
}
else { // add item to keychain
writeErr = SecItemAdd((CFDictionaryRef)dictForAdd, NULL);
if (writeErr != errSecSuccess) {
NSLog(@"Add KeyChain Item Error!!! Error Code:%ld", writeErr);
[dictForAdd release];
return NO;
}
else {
NSLog(@"Add KeyChain Item Success!!!");
[dictForAdd release];
return YES;
}
}
[dictForAdd release];
return NO;
}
上面代码中,首先构建一个要添加到KeyChain中数据的Dictionary,包含一些基本的KeyChain Item的数据类型,描述,访问分组以及最重要的数据等信息,最后通过调用SecItemAdd方法将我们需要保存的UUID保存到KeyChain中。
获取KeyChain中相应数据的代码如下:
+ (NSString*)getUDIDFromKeyChain
{
NSMutableDictionary *dictForQuery = [[NSMutableDictionary alloc] init];
[dictForQuery setValue:(id)kSecClassGenericPassword forKey:(id)kSecClass];
// set Attr Description for query
[dictForQuery setValue:[NSString stringWithUTF8String:kKeychainUDIDItemIdentifier]
forKey:kSecAttrDescription];
// set Attr Identity for query
NSData *keychainItemID = [NSData dataWithBytes:kKeychainUDIDItemIdentifier
length:strlen(kKeychainUDIDItemIdentifier)];
[dictForQuery setObject:keychainItemID forKey:(id)kSecAttrGeneric];
// The keychain access group attribute determines if this item can be shared
// amongst multiple apps whose code signing entitlements contain the same keychain access group.
NSString *accessGroup = [NSString stringWithUTF8String:kKeyChainUDIDAccessGroup];
if (accessGroup != nil)
{
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[dictForQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
}
[dictForQuery setValue:(id)kCFBooleanTrue forKey:(id)kSecMatchCaseInsensitive];
[dictForQuery setValue:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
[dictForQuery setValue:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
OSStatus queryErr = noErr;
NSData *udidValue = nil;
NSString *udid = nil;
queryErr = SecItemCopyMatching((CFDictionaryRef)dictForQuery, (CFTypeRef*)&udidValue);
NSMutableDictionary *dict = nil;
[dictForQuery setValue:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];
queryErr = SecItemCopyMatching((CFDictionaryRef)dictForQuery, (CFTypeRef*)&dict);
if (queryErr == errSecItemNotFound) {
NSLog(@"KeyChain Item: %@ not found!!!", [NSString stringWithUTF8String:kKeychainUDIDItemIdentifier]);
}
else if (queryErr != errSecSuccess) {
NSLog(@"KeyChain Item query Error!!! Error code:%ld", queryErr);
}
if (queryErr == errSecSuccess) {
NSLog(@"KeyChain Item: %@", udidValue);
if (udidValue) {
udid = [NSString stringWithUTF8String:udidValue.bytes];
}
}
[dictForQuery release];
return udid;
}
上面代码的流程也差不多一样,首先创建一个Dictionary,其中设置一下查找条件,然后通过SecItemCopyMatching方法获取到我们之前保存到KeyChain中的数据。
四、总结
本文介绍了使用KeyChain实现APP删除后依然可以获取到相同的UDID信息的解决方法。
你可能有疑问,如果系统升级以后,是否仍然可以获取到之前记录的UDID数据?
答案是肯定的,这一点我专门做了测试。就算我们程序删除掉,系统经过升级以后再安装回来,依旧可以获取到与之前一致的UDID。但是当我们把整个系统还原以后是否还能获取到之前记录的UDID,这一点我觉得应该不行,不过手机里面数据太多,没有测试,如果大家有兴趣可以测试一下,验证一下我的猜想。
完整代码地址: https://github.com/smileEvday/SvUDID
大家如果要在真机运行时,需要替换两个地方:
第一个地方是plist文件中的accessGroup中的APPIdentifier。
第二个地方是SvUDIDTools.m中的kKeyChainUDIDAccessGroup的APPIdentity为你所使用的profile的APPIdentifier。
文章和代码中如果有什么不对的地方,欢迎指正,在这儿先谢过了。
原文:
http://www.cnblogs.com/smileEvday/p/UDID.html
分享到:
相关推荐
获取UUID的方法通常是创建一个新的UUID实例,然后将其保存到UserDefaults或者KeyChain中。使用UserDefaults虽然简单,但不够安全,因为用户可以清除这些数据。因此,我们通常选择KeyChain,一个提供安全存储用户敏感...
- 为了获取设备的UDID,你需要在设备上连接iTunes,并查看其摘要信息,或者使用第三方工具如iBackupViewer。 - 将UDID复制并粘贴到相应位置,然后保存设置。 2. **创建并配置开发者证书**: - 回到苹果开发者...
如果需要获取更稳定的设备唯一标识,可以尝试使用`CFUUIDCreate`函数创建一个UUID(通用唯一标识符),并保存到Keychain中,这样可以在应用的生命周期内保持一致。然而,苹果自iOS 7开始限制了获取IMEI和UDID(唯一...
在使用Devicelib时,开发者需要导入库并调用相关方法来获取和保存设备标识符。具体代码实现可能会包含创建Keychain的访问组,设置不允许删除的属性,然后写入自定义的设备标识符。这样,即使用户卸载应用程序,只要...
如果没有,就创建一个,并保存到Keychain。 ```swift if let existingDeviceID = keychain.object(forKey: kSecValueData) as? Data { // 使用现有的device ID } else { let deviceID = UUID().uuidString ...
由于它可能被用于跟踪用户,暴露个人隐私,苹果在iOS 5之后禁止了直接获取UDID。 2. **MAC地址**: MAC地址是网络接口控制器的物理地址,通常为6字节的唯一标识。然而,自iOS 7开始,苹果不再允许访问真实的MAC...
- **使用Keychain Access**:在Mac OS系统中,打开“钥匙串访问”工具,然后选择“证书助理”(Certificate Assistant)选项,接着选择“从证书代理请求证书”(Request a Certificate From a Certificate Authority...
你可以通过Xcode的Organizer或iTunes获取设备的UDID。在苹果开发者网站的相应页面关联此UDID,以便在真机上运行应用。 5. **创建APP ID**: 为你的应用程序创建一个APP ID,这可以是特定于某个应用的,也可以是...
这通常意味着在使用UUID时,不会持久化任何可以追踪到用户身份的信息,且用户应有权选择是否允许应用获取和使用UUID。 总的来说,UUID.zip提供的是一套实用的工具,帮助开发者在不违反苹果政策的前提下,获取并存储...
SNUUIDIOS equipment identification, which still ...为解决这个问题,因此我们采用keychain保存app第一次生成的uuid,保证每次的uuid都是一样的,即使app被删除仍然相同。使用方法:NSString *uuid = [SNUUID uuid];
- **定位唯一设备ID**:可以使用Xcode、iTunes或iPhone Configuration Utility来获取设备的UDID(Unique Device Identifier)。 - **添加单个设备**:登录Apple Developer账户,在设备管理页面手动添加设备的UDID。 - ...
- **配置应用服务**:为应用 ID 启用 Apple Push Notification Service (APNs)、In-App Purchase、Game Center 和 Keychain Data Sharing 等服务。 #### 开发与预览版本安装 - **创建开发预配文件**:为应用创建...
苹果企业证书签名是一种用于在iOS设备...总之,苹果企业证书签名涉及多个步骤,包括创建配置文件、获取和导入证书、设置和使用密码,以及最后的签名和分发。这个过程确保了企业可以安全、便捷地向其成员分发内部应用。
1. **获取UDID**:UDID是设备的唯一标识符,可通过多种方式获取。 2. **注册数量限制**:依据开发者账号的不同等级,注册设备的数量也有所不同。 #### 五、生成签名文件(Provision Profile) 签名文件是确保应用...
这包括输入设备的UDID(唯一设备标识符),通常可通过Xcode的Organizer窗口获取。 6. **创建App ID**:在“Identifiers”部分,创建一个App ID,它是应用的唯一标识。遵循特定的命名规则,如`...
DeviceToken是由UDID(设备唯一标识)和应用的Bundle Identifier生成的,是推送的关键。 在实际操作中,首先需要在Apple Developer Portal上创建App ID和描述文件。App ID是用来识别应用程序的唯一字符串,通常形式...