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就是你的签名中最前面的那部分。

  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的,所以才行了预编译宏来选择性添加。

三、使用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://blog.sina.com.cn/s/blog_6f9200bd0101faz4.html]

iOS 7.0获取iphone UDID 【转】的更多相关文章

  1. iOS 测试之非代码获取 iPhone 型号及其他信息

    首先 安装libimobiledevice和ideviceinstaller $ brew uninstall ideviceinstaller $ brew uninstall libimobile ...

  2. iOS获取iPhone系统等信息和服务器返回空的异常处理

    前言: 在项目中经常会遇到需要获取系统的信息来处理一些特殊的需求和服务端返回为空的处理,写在这里只是笔记一下. 获取设备的信息 NSLog(@"globallyUniqueString=%@ ...

  3. iOS开发之 -- 获取设备的唯一标示符

    各种获取设备唯一标识的方法介绍 一.UDID(Unique Device Identifier) UDID的全称是Unique Device Identifier,它就是苹果iOS设备的唯一识别码,它 ...

  4. iOS 10.0 更新点(开发者视角)

    html, body {overflow-x: initial !important;}html { font-size: 14px; } body { margin: 0px; padding: 0 ...

  5. iOS之iOS11、iPhone X、Xcode9 适配指南

    更新iOS11后,发现有些地方需要做适配,整理后按照优先级分为以下三类: 1.单纯升级iOS11后造成的变化: 2.Xcode9 打包后造成的变化: 3.iPhoneX的适配 一.单纯升级iOS11后 ...

  6. apicloud编译所需的ios证书的获取方法

    在我们通过apicloud或hbuilderX这些工具打包ios应用的时候,需要一个ios证书. 那么我们如何生成这个ios证书呢?网上介绍的方法都是需要使用mac电脑,然后用mac电脑的钥匙串访问的 ...

  7. iOS常用系统信息获取方法

    一.手机电量获取,方法二需要导入头文件#import<objc/runtime.h> 方法一.获取电池电量(一般用百分数表示,大家自行处理就好) -(CGFloat)getBatteryQ ...

  8. (IOS)Swift2.0 Radio 程序分析

    本文主要分享下楼主在学习Swift编程过程中,对GitHub上的一个开源项目Swift Radio的研究心得. 项目地址:https://github.com/swiftcodex/Swift-Rad ...

  9. iOS:关于获取网络类型和运营商信息

    目录 1. 获取运营商网络类型 2. 获取运营商信息 返回目录 1. 获取运营商网络类型 Apple的Reachability Sample看起来不错,但是只可以判断是否连接到互联网和是否连接Wifi ...

随机推荐

  1. Git基本命令

    获取master: git clone ssh://some.i.p/some/source/~/somerep 获取branch: git clone -b branch-version ssh:/ ...

  2. 获取在线人数 CNZZ 和 51.la

    string Cookies = string.Empty; /// <summary> /// 获取在线人数 (51.la统计器) /// </summary> /// &l ...

  3. 在网页标题栏上和收藏夹显示网站logo

    第一步,准备一个图标制作软件. 首先您必须了解所谓的图标(Icon)是一种特殊的图形文件格式,它是以.ico 作为扩展名.普通的图像设计软件无法使用这种格式,所以您需要到下载一个ico图标工具,本站常 ...

  4. LinearLayout和RelativeLayout 区别

    LinearLayout和RelativeLayout转自:http://blog.csdn.net/w176236767/article/details/6605848共有属性:java代码中通过b ...

  5. 读书笔记:应用随机过程:概率模型导论:Aloha协议问题

    例4.16,Aloha协议:就本书例题所涉及的部分来说,几乎等同于CSMA.这个例题重写如下: 考察一个包含多个设备的通信系统,其中在每个时间段发送信息的设备个数是独立同分布的.......每个设备将 ...

  6. DateGridView中添加下拉框列并实现数据绑定、更改背景色

    1.添加下拉框 代码实现==> using System; using System.Collections.Generic; using System.Windows.Forms; names ...

  7. Android中的布局优化方法

    http://blog.csdn.net/rwecho/article/details/8951009 Android开发中的布局很重要吗?那是当然.一切的显示样式都是由这个布局决定的,你说能不重要吗 ...

  8. [linux basic 基础]----线程的属性

    在信号量和互斥量例子中,我们都是在程序推出之前利用pthread_join对线程进行再次同步:如果想让thread想创建它的线程返回数据我需要这么做:问题:我们有时候既不需要第二个线程向main线程返 ...

  9. 尽量使用条件属性(Conditional Attribute)而不是#if/#endif预处理

    http://www.cnblogs.com/JiangSoney/archive/2009/08/10/1543197.html .net框架提供了一个特性:属性(Attribute),注意:此属性 ...

  10. Servlet概述及其生命周期

    Servlet和传统CGI程序相比的优点:   1. 只需要启动一个操作系统进程以及加载一个JVM,大大降低了系统的开销 2. 如果多个请求需要做同样处理的时候,这时只需要加载一个类,这也大大降低了开 ...