本文是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 keychainwriteErr = 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 queryNSData *keychainItemID = [NSData dataWithBytes:kKeychainUDIDItemIdentifierlength: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

ios使用KeyChain获取唯一不变的udid相关推荐

  1. swift获取openuuid_iOS获取设备唯一标识:UDID、IDFA、IDFV、OpenIDFA、SimulateIDFA等.

    1. UDID UDID全称是Unique Device Identifier,顾名思义,它就是苹果iOS设备的唯一标识码,它由40个字符的字母和数字组成.在很多需要限制一台设备一个张红的应用中经常会 ...

  2. 查看UDID的两种方式,连手机查看,不用Mac连接iPhone手机,获取UDID, iOS 设备的一个唯一识别码

    什么是UDID? UDID,是 iOS 设备的一个唯一识别码,每台 iOS 设备都有一个独一无二的编码,这个编码,我们称之为识别码,也叫做UDID( Unique Device Identifier) ...

  3. iOS - 获取设备标识符UUID/UDID/IMEI等

    关于获取设备各种标识符,最近整理了一下.基本上有IDFA.IDFV.IMEI.IMSI.UUID.UDID.MAC地址; 想要获取系统唯一标识符的话,比如说不管用户卸载还是重新安装都可以唯一标识的可以 ...

  4. Android Q 获取设备唯一ID(UDID\GUID\UUID\SSAID\GAID)

    Android Q获取设备唯一ID(UDID\GUID\UUID\SSAID\GAID) 一.简介 1.1 问题背景 1.2 关键技术 二.解决方案 2.1 谷歌官方推荐方案 (4种) 2.2 实现方 ...

  5. ios下 KeyChain 的研究

    iOS的keyChain是一个相对独立的空间,当我们的程序(App)被替换或者删除时并不会删除保存在keyChain的内容.相对于NSUserDefaults.plist文件保存等一般方式,keych ...

  6. php 获取手机设备的ID,获取苹果设备的UDID

    苹果开发中,经常会添加测试设备UDID,通常在fir.蒲公英等平台获取,除了此类平台我们自己也可以实现UDID的获取,下面就通过已有苹果开发证书和模板来获取苹果设备的UDID. 先体验一下:获取UDI ...

  7. iOS中Keychain保存用户名和密码

    摘要: 有用户就用用户名和密码,而现在的应用都少不了一个保存用户名和密码用于自动登录的功能,本文介绍使用iOS自带的Keychain方法保存用户名和密码. 说到保存用户名和密码,以前有用过本地的数据库 ...

  8. ios实现wifi搜索连接_Wifi 定位原理及 iOS Wifi 列表获取

    Wifi 定位原理及 iOS Wifi 列表获取 对于大家来说,Wifi 应该是一个很熟悉的词了,我们每天都可能在使用 Wifi 热点.Wifi 除了能给我们提供热点之外同时还有定位的作用, 现在移动 ...

  9. 【IOS游戏开发】知识普及之UDID

    1.什么是UDID UDID 是由子母和数字组成的40个字符串的序号,用来区别每一个唯一的iOS设备,包括 iPhones, iPads, 以及 iPod touches,这些编码看起来是随机的,实际 ...

最新文章

  1. 妙啊!不怕遮挡的图像线段匹配 SOLD2,还能联合自监督线段检测|CVPR2021 Oral
  2. 如何防止在listbox中添加很多数据出现不停的刷新
  3. 怎样远程访问 MySQL
  4. bzoj 4017: 小Q的无敌异或(线段树)
  5. XML Schema帮你建模
  6. (转)javascript 从数组中删除指定值(不是指定位置)的元素
  7. oracle10g最快安装教程,史上最详细Oracle 10g安装教程
  8. 端口号从8080变成8081,cmd关闭8080端口
  9. apk双开多开分身术的原理
  10. 如何将dwt模板移到php,如何制作ECSHOP模板
  11. js 中实现百分比计算
  12. python 语音识别培训使用Python和Keras创建简单语音识别引擎
  13. omnigraffle 画曲线_三步学会用绘图利器 OmniGraffle 绘制流程图
  14. 欧几里德关系的S5---刘易斯逻辑之十一
  15. JAVA中的糕富帅技术——反射(一)
  16. 一些常用的编程语言和他们的特点
  17. VCM音圈马达的一些特点
  18. teamcenter快速入门
  19. 用Python实现一个决策树分类器
  20. U盘格式化时分配单元的大小的设置

热门文章

  1. Mybatis源码解析之Mybatis初始化过程
  2. 三十一、深入Python中的正则表达式
  3. 深入解读首个万亿级语言模型Switch Transformer
  4. ICLR 2020 | 可提速3000倍的全新信息匹配架构(附代码复现)
  5. 基于知识图谱的问答系统入门—NLPCC2016KBQA数据集
  6. 腾讯医疗AI实验室:3篇论文被国际顶尖会议收录
  7. mvc中ajax的跳转,springmvc ajax 跳转的学习笔记
  8. idea debug调试_大牛都会用的 IDEA 调试技巧!!!
  9. PHPMySQL——防止SQL注入-代码片段
  10. CG CTF WEB Download~!