本文是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

转载于:https://www.cnblogs.com/wcLT/p/4738393.html

如何使用KeyChain保存和获取UDID相关推荐

  1. python获取机器唯一标识_开发中常用工具 - 获取设备的唯一标识、UDID、UUID、keychain保存UUID、判断网络...

    UDID 全名:Unique Device Identifie(设备唯一标识符) 说明:UDID,即设备唯一标识符,这是除序列号之外每台iOS设备的独一无二的号码.UDID只是和设备相关的,是用来区分 ...

  2. iOS 开发 -- 使用KeyChain保存用户名、密码并实现自动登录

    一.前言 我的话,只是写了个keychain使用的工具类,让我们使用的时候可以直接调用接口,以求方便. 但是关于keychain的一些概念还有一些官方API我都不打算说的,当然你要看下面一些东西的话可 ...

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

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

  4. iOS获取UDID问题

    首先,我承认我骗人了,目前iOS已经无法再获取UDID了,没有办法做到唯一标识一个设备了.目前有一些替代的方案,我在这里将一个我认为最优的写在这里,欢迎大家指正. 首先介绍几个知识点: 1.CFUUI ...

  5. 微信小程序云数据库带换行的文本保存和获取

    当保存一段带换行和空格的文本到数据库时,会自动的将换行和空格进行转成&hc,就导致下一次从数据库中调取文本的时候,之前的换行变成了&hc. 所以在保存和获取的时候,要将换行符进行修改. ...

  6. iOS系统 查看设备序列号 获取UDID

    iOS系统的设备标识即UDID,长度为40位,如:85f3c1bcb9ca8ad8b2931219072cf05382a6da09 获取UDID,可以使用手机端访问UDID查询网站在线查询,也可以通过 ...

  7. iphone手机获取udid最简单的方法

    苹果官方推荐使用itunes来获取udid,但这个方法太麻烦,需要测试人员每个测试人员都去安装itunes或手机助手去获取udid非常麻烦. 因此,我们需要一个测试人员,访问一个链接就可以获取udid ...

  8. iPhoneXS Max 获取UDID

    之前获取苹果设备的UDID都是连接手机到电脑,打开itunes点击几下序列号即可查看到,可当XS Max连接上的时候是看不到的. 经过查阅,苹果官方给出了一种获取本地设备UDID的方法,连接真机后,可 ...

  9. 苹果手机快速获取UDID方式

    1.苹果手机扫描二维码或Safari打开网址(https://www.pgyer.com/udid) 注:请用苹果自带的Safari浏览器打开网址 2.点击获取UDID                ...

  10. iPhone手机轻松获取UDID的六种方式

    Qinz UDID是什么? you的I的,不就是你的我的,大家都可以用的,恩,肯定是公共的!其实不然,它可是唯一的哦?!言归正传:UDID是一种iOS设备的特殊识别码.除序号之外,每台iOS装置都另有 ...

最新文章

  1. FileSystemWatcher使用方法具体解释
  2. shopnc 支持 支付宝快捷登陆 shopnc权限验证原理说明
  3. 计算机网络专业的基础知识,计算机网络专业基础知识.doc
  4. eclipse调出server_eclipse在server中tomcat server找不到的问题
  5. abaqus单位_abaqus系列技巧3:关于有限元软件的单位制问题
  6. 关于 AfxSocketInit() 的使用
  7. JMeter的PUT请求,响应结果中文出现乱码的解决方法
  8. SICK TiM561激光雷达的使用
  9. webbrowser加载地图网页出现脚本错误解决
  10. 26岁零基础转行学习前端可以找到工作吗?
  11. Python、Java、C#、Perl 创始人聚首,编程语言要变天?
  12. 2018-10-27
  13. python3.5之输出HTML实体字符
  14. oc基础-oc中之集合NSSet,NSMutableSet
  15. 苹果6代掉水里怎么办 iPhone6掉水处理方法
  16. 如何在 Mac 上更改文件夹颜色,改变 mac 文件夹颜色教程
  17. 使用nodejs-koa2-mysql-sequelize-jwt 实现项目api接口
  18. js 除了自己获取其他所有兄弟节点
  19. 编译 ORB-SLAM2/3的ROS工程造成(You should double-check your ROS_PACKAGE_PATH...)
  20. win10系统下如何打开bios如何打开Intel-VT-x教程

热门文章

  1. 人工智能称霸围棋,吾感觉不可思议
  2. php 中文字符串长度_php中计算中文字符串长度、截取中文字符串的函数代码
  3. mongodb lbs java_Spring-MongoDB与LBS
  4. 灌浆料的弹性模量计算_cgm-c80风电基础用灌浆料
  5. c++ extern学习
  6. oracle 列出asm磁盘,如何列出ASM磁盘组的所有磁盘
  7. python圣诞树代码_用Python画一棵带音乐的雪夜圣诞树
  8. WCDMA中的TSTD和STTD
  9. HashTable与ConcurrentHashMap的区别
  10. [Erlang 0074] Erlang 杂记 IV