前言:
1 .为了统计和检测应用的使用数据,几乎每家公司都有获取唯一标识的业务需求,在iOS5以前获取唯一标识,可以获取到系统提供的方法UDID(Unique Device Identifier),后来被出于用户隐私的考虑被Apple官方禁止掉了。于是,大家开始在iOS6中使用 MAC 地址(Medium/Media Access Control) ,后来又被Apple官方在iOS7中禁止掉了。苹果及其国外的IT公司都会比较注重用户隐私,所以今后一但有比较靠谱的获取唯一标示的方法放出,苹果肯定会封堵。
2.** ** 在非越狱的手机上获取某个硬件信息生成唯一标识,第一只能找到苹果的漏洞,第二就是调用一些私有接口,显然这两条路都比较艰难,并不可持续发展,所以网上大部分的唯一标识都是从操作系统层面获取的,在重置手机系统的时候都会被清除,在系统升级、卸载重装、备份恢复都可以保留,现在本人尚未发现可以使用严格意义上的唯一标识。接下来我想跟大家探讨的是如何通过“合法”的手段来尽量拿到不会轻易发生变化的“唯一标识”。
3.** **在2013年3月21日苹果已经通知开发者,从2013年5月1日起,访问UIDID的应用将不再能通过审核,替代的方案是开发者应该使用“在iOS 6中介绍的Vendor或Advertising标示符”。
4.MAC地址不能再用来设别设备
** **还有一个生成iOS设备唯一标示符的方法是使用iOS设备的Media Access Control(MAC)地址。一个MAC地址是一个唯一的号码,它是物理网络层级方面分配给网络适配器的。这个地址苹果还有其他的名字,比如说是硬件地址(Hardware Address)或是Wifi地址,都是指同样的东西。
有很多工程和框架都使用这个方法来生成唯一的设备ID。比如说ODIN。然而,苹果并不希望有人通过MAC地址来分辨用户,所以如果你在iOS7系统上查询MAC地址,它现在只会返回02:00:00:00:00:00。
5 ** ** 讲真苹果这傲娇的小脾气什么时候能能改改,不过这样 对于用户隐私的保护,确实起到很大作用,而且苹果也没有把路堵死,现在苹果明确的表明你应该使用-[UIDevice identifierForVendor]或是-[ASIdentifierManager advertisingIdentifier]来作为你框架和应用的唯一标示符。坦白的来说,应对这些变化也不是那么的难,见以下代码片段:
:
NSString*identifierForVendor=[[UIDevice currentDevice].identifierForVendor UUIDString];NSString*identifierForAdvertising=[[ASIdentifierManager sharedManager].advertisingIdentifier UUIDString];
但是这样用法有个缺点,就是程序每次被删除以后,获取到的都是新的uuid,并不能实现每个手机的唯一性.
所以下面要讲的就是这个问题的解决办法.
** **对于这个问题有一个可行的办法,就是把获取到的uuid保存在钥匙串里面,这样即使程序删除重装,获取到的uuid一直是同一个,实现了手机的唯一标识的作用.
保存钥匙串 我们需要用到keychain,除此之外,Code Signing Entitlements的创建方法也不够严谨。下面教大家一种方法,简单快速.
1.新建一个工程,看一下自己的Bundle Id.这个Bundle Id 要和你用真机测试时的证书上面的Bundle Id相匹配。
注意这个Bundle identifier
2.Target - Capabilities - Keychain Sharing - ON
注意圆圈里面的保持一致
这步主要目的是打开Keychain Sharing,将它由灰色状态的OFF改为蓝色状态的ON。
会自动生成这个
左侧的目录会自动生成Entitlements文件,不需要自己创建了。
也就是说,Bundle Identifier、Keychain Sharing的Keychain Groups、Entitlements文件的Keychain Access Groups的第一个元素,它们要保持上图所示的一致性。
设置好了以后可以运行下程序,没问题可以进行下一步。
.传说中的uuid类和keychain类来啦
既然苹果的keychain方法会崩溃而且有些复杂,我们只保存一个uuid的话可以用下面的简单方法:
KeyChainStore.h#import@interfaceKeyChainStore:NSObject+(void)save:(NSString*)service data:(id)data;+(id)load:(NSString*)service;+(void)deleteKeyData:(NSString*)service;@end
KeyChainStore.m//// KeyChainStore.m// getUUID//// Created by ckl@pmm on 16/9/18.// Copyright © 2016年 CKLPronetway. All rights reserved.//#import"KeyChainStore.h"@implementationKeyChainStore+(NSMutableDictionary*)getKeychainQuery:(NSString*)service{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 dictionaryNSMutableDictionary*keychainQuery=[selfgetKeychainQuery:service];//Delete old item before add new itemSecItemDelete((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 dictionarySecItemAdd((CFDictionaryRef)keychainQuery,NULL);}+(id)load:(NSString*)service{id ret=nil;NSMutableDictionary*keychainQuery=[selfgetKeychainQuery: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);returnret;}+(void)deleteKeyData:(NSString*)service{NSMutableDictionary*keychainQuery=[selfgetKeychainQuery:service];SecItemDelete((CFDictionaryRef)keychainQuery);}@end
getUUID.h#import@interfacegetUUID:NSObject+(NSString*)getUUID;@end
getUUID.m//// getUUID.m// getUUID//// Created by ckl@pmm on 16/9/18.// Copyright © 2016年 CKLPronetway. All rights reserved.//#import"getUUID.h"#import"KeyChainStore.h"#defineKEY_USERNAME_PASSWORD @"com.company.app.usernamepassword"#defineKEY_USERNAME @"com.company.app.username"#defineKEY_PASSWORD @"com.company.app.password"@implementationgetUUID+(NSString*)getUUID{NSString*strUUID=(NSString*)[KeyChainStore load:@"com.company.app.usernamepassword"];//首次执行该方法时,uuid为空if([strUUID isEqualToString:@""]||!strUUID){//生成一个uuid的方法CFUUIDRef uuidRef=CFUUIDCreate(kCFAllocatorDefault);strUUID=(NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault,uuidRef));//将该uuid保存到keychain[KeyChainStore save:KEY_USERNAME_PASSWORD data:strUUID];}returnstrUUID;}@end
在Viewcontroller里面执行如下代码
#import"ViewController.h"#import"getUUID.h"@interfaceViewController()@end@implementationViewController-(void)viewDidLoad{[superviewDidLoad];NSLog(@" uuid is ---> %@",[getUUID getUUID]);// Do any additional setup after loading the view, typically from a nib.}
打印出来类似于以下的长串字符:
获取到的uuid.png
把程序卸载掉然后重新运行一次,获取到的还是上次保存的uuid.
不知道手机越狱以后,会不会改变,因为楼主手机版本是最新的9.3.5,身边还没相关越狱设备,希望大家可以自行测试一下.并告知楼主.联系方式 : QQ :576484150
参考地址:
:http://blog.sina.com.cn/s/blog_5971cdd00102vqgy.html
:http://blog.csdn.net/chy555chy/article/details/51628079
:https://www.jianshu.com/p/29572b756d26
写了一个demo,已经放在gitHub上了,有兴趣的同学可以下载下来瞅瞅.
demo 地址 :https://github.com/chengkunlun/getUUID