前言:
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相匹配。
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 <Foundation/Foundation.h>
@interface KeyChainStore : 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"
@implementation KeyChainStore
+ (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 dictionary
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Delete old item before add new item
SecItemDelete((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 dictionary
SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}
+ (id)load:(NSString *)service {
id ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery: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);
return ret;
}
+ (void)deleteKeyData:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((CFDictionaryRef)keychainQuery);
}
@end
getUUID.h
#import <Foundation/Foundation.h>
@interface getUUID : 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"
#define KEY_USERNAME_PASSWORD @"com.company.app.usernamepassword"
#define KEY_USERNAME @"com.company.app.username"
#define KEY_PASSWORD @"com.company.app.password"
@implementation getUUID
+(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];
}
return strUUID;
}
@end
在Viewcontroller里面执行如下代码
#import "ViewController.h"
#import "getUUID.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@" uuid is ---> %@",[getUUID getUUID]);
// Do any additional setup after loading the view, typically from a nib.
}
打印出来类似于以下的长串字符:
把程序卸载掉然后重新运行一次,获取到的还是上次保存的uuid.
不知道手机越狱以后,会不会改变,因为楼主手机版本是最新的9.3.5,身边还没相关越狱设备,希望大家可以自行测试一下.并告知楼主.联系方式 : QQ :576484150
参考地址:
:http://blog.sina.com.cn/s/blog_5971cdd00102vqgy.html
:http://blog.csdn.net/chy555chy/article/details/51628079
自己写了一个demo,已经放在gitHub上了,有兴趣的同学可以下载下来瞅瞅.
demo 地址 :https://github.com/chengkunlun/getUUID