首先,我承认我骗人了,目前iOS已经无法再获取UDID了,没有办法做到唯一标识一个设备了。目前有一些替代的方案,我在这里将一个我认为最优的写在这里,欢迎大家指正。
首先介绍几个知识点:
CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);NSString *cfuuidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfuuid));
获得的这个CFUUID值系统并没有存储。每次调用CFUUIDCreate,系统都会返回一个新的唯一标示符。如果你希望存储这个标示符,那么需要自己将其存储到NSUserDefaults, Keychain, Pasteboard或其它地方。
NSString *uuid = [[NSUUID UUID] UUIDString];
跟CFUUID一样,这个值系统也不会存储,每次调用的时候都会获得一个新的唯一标示符。如果要存储的话,你需要自己存储。
这是iOS 6中另外一个新的方法,advertisingIdentifier是新框架AdSupport.framework的一部分。ASIdentifierManager单例提供了一个方法advertisingIdentifier,通过调用该方法会返回一个上面提到的NSUUID实例。
NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
广告标示符是由系统存储着的。不过即使这是由系统存储的,但是有几种情况下,会重新生成广告标示符。如果用户完全重置系统((设置程序 -> 通用 -> 还原 -> 还原位置与隐私) ,这个广告标示符会重新生成。另外如果用户明确的还原广告(设置程序-> 通用 -> 关于本机 -> 广告 -> 还原广告标示符) ,那么广告标示符也会重新生成。关于广告标示符的还原,有一点需要注意:如果程序在后台运行,此时用户“还原广告标示符”,然后再回到程序中,此时获取广告标示符并不会立即获得还原后的标示符。必须要终止程序,然后再重新启动程序,才能获得还原后的广告标示符。之所以会这样,我猜测是由于ASIdentifierManager是一个单例。
这种叫法也是在iOS 6中新增的,不过获取这个IDFV的新方法被添加在已有的UIDevice类中。跟advertisingIdentifier一样,该方法返回的是一个NSUUID对象。
NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
苹果官方的文档中对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.
如果满足这样的条件,那么获取到的这个属性值就不会变:相同的一个程序里面-相同的vindor-相同的设备。如果是这样的情况,那么这个值是不会相同的:相同的程序-相同的设备-不同的vindor,或者是相同的程序-不同的设备-无论是否相同的vindor。
看完上面的内容,我有这样的一个疑问“vendor是什么”。我首先想到的是苹果开发者账号。但事实证明这是错误的。接着我想可能是有一个AppIdentifierPrefix东西,跟钥匙串访问一样,可以在多个程序间共享。同样,这个想法也是的。最后证明,vendor非常简单:一个Vendor是CFBundleIdentifier(反转DNS格式)的前两部分。例如,com.doubleencore.app1 和 com.doubleencore.app2 得到的identifierForVendor是相同的,因为它们的CFBundleIdentifier 前两部分是相同的。不过这样获得的identifierForVendor则完全不同:com.massivelyoverrated 或 net.doubleencore。
在这里,还需要注意的一点就是:如果用户卸载了同一个vendor对应的所有程序,然后在重新安装同一个vendor提供的程序,此时identifierForVendor会被重置。
NSString *udid = [[UIDevice currentDevice] uniqueIdentifier];
NSString *openUDID = [OpenUDID value];
OpenUDID利用了一个非常巧妙的方法在不同程序间存储标示符 — 在粘贴板中用了一个特殊的名称来存储标示符。通过这种方法,别的程序(同样使用了OpenUDID)知道去什么地方获取已经生成的标示符(而不用再生成一个新的)。是不是感觉这个就是你想要的,但是,UIPasteboard由共享变为沙盒化了,只对相同的app group可见。所以现在OpenUDID基本只能保证同一个APP的openUDID一样了
可以自定义APP的剪切板
我们可以把KeyChain理解为一个Dictionary,所有数据都以key-value的形式存储,可以对这个Dictionary进行add、update、get、delete这四个操作。对于每一个应用来说,KeyChain都有两个访问区,私有区和公共区。私有区是一个sandbox,本程序存储的任何数据都对其他程序不可见。而要想在将存储的内容放在公共区,需要先声明公共区的名称,官方文档管这个名称叫“keychain access group”。现在用Xcode很方便可以配置。但是公共区也是相同的app group才可以共享。操作keychain,推荐使用SSKeychain:https://github.com/soffes/sskeychain。keychain默认是不同步到iCloud上的,除非该item设置了key:kSecAttrSynchronizable。
下面正餐来了:
1、其中主要是使用钥匙串中的UUID,剪切板和userDefaults都是为了备份UUID,以防止钥匙串中信息失效。
2、生成UUID的顺序是IDFA -> IDFV -> 获取UUID。原因是IDFA如果有的话是设备唯一(除非还原),IDFV是同一个vendor一样,尽量使用作用域比较大的值作为唯一标示。
下面不说废话,直接上代码:
//
// DLUDID.m
// DLUDID
//
// Created by zhangdali on 16/5/5.
// Copyright © 2016年 zhangdali. All rights reserved.
//
#import "DLUDID.h"
#import
#import
#import "SSKeychain.h"
#define kUDIDService @"com.dali.udid"
#define kUDIDKey @"com.dali.udid"
#define DL_IS_STR_NIL(objStr) (![objStr isKindOfClass:[NSString class]] || objStr == nil || [objStr length] <= 0)
static NSString *_udid;
@implementation DLUDID
#pragma mark - generate UUID
/**
* get IDFA
*
* @return IDFA
*/
+ (NSString *)appleIDFA {
NSString *idfa = nil;
Class ASIdentifierManagerClass = NSClassFromString(@"ASIdentifierManager");
if (ASIdentifierManagerClass) { // a dynamic way of checking if AdSupport.framework is available
SEL sharedManagerSelector = NSSelectorFromString(@"sharedManager");
id sharedManager = ((id (*)(id, SEL))[ASIdentifierManagerClass methodForSelector:sharedManagerSelector])(ASIdentifierManagerClass, sharedManagerSelector);
SEL advertisingIdentifierSelector = NSSelectorFromString(@"advertisingIdentifier");
NSUUID *advertisingIdentifier = ((NSUUID* (*)(id, SEL))[sharedManager methodForSelector:advertisingIdentifierSelector])(sharedManager, advertisingIdentifierSelector);
idfa = [advertisingIdentifier UUIDString];
}
return idfa;
}
/**
* get IDFV
*
* @return IDFV
*/
+ (NSString *)appleIDFV {
if(NSClassFromString(@"UIDevice") && [UIDevice instancesRespondToSelector:@selector(identifierForVendor)]) {
// only available in iOS >= 6.0
return [[UIDevice currentDevice].identifierForVendor UUIDString];
}
return nil;
}
/**
* get UDID
*
* @return UDID
*/
+ (NSString *)randomUDID {
if(NSClassFromString(@"NSUUID")) { // only available in iOS >= 6.0
return [[NSUUID UUID] UUIDString];
}
CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
CFStringRef cfuuid = CFUUIDCreateString(kCFAllocatorDefault, uuidRef);
CFRelease(uuidRef);
NSString *uuid = [((__bridge NSString *) cfuuid) copy];
CFRelease(cfuuid);
return uuid;
}
#pragma mark - keychain
/**
* get UDID in keychain
*
* @return UDID
*/
+ (NSString *)UDIDInKeychain {
return [SSKeychain passwordForService:kUDIDService account:kUDIDKey];
}
/**
* save UDID to keychain
*
* @param udid
*/
+ (void)saveUDIDToKeychain:(NSString *)udid {
[SSKeychain setPassword:udid forService:kUDIDService account:kUDIDKey];
}
#pragma mark - UIPasteboard
/**
* get UDID in pasteboard
*
* @return UDID
*/
+ (NSString *)UDIDInPasteboard {
UIPasteboard *pb = [UIPasteboard pasteboardWithName:kUDIDService create:NO];
if (!pb) {
return nil;
}
id data = [pb dataForPasteboardType:kUDIDKey];
NSString *udid = nil;
if (data) {
udid = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
return udid;
}
/**
* save UDID to pasteboard
*
* @param udid
*/
+ (void)saveUDIDToPasteboard:(NSString *)udid {
UIPasteboard *pb = [UIPasteboard pasteboardWithName:kUDIDService create:YES];
[pb setPersistent:YES];
NSData *data = [udid dataUsingEncoding:NSUTF8StringEncoding];
[pb setData:data forPasteboardType:kUDIDKey];
}
#pragma mark - NSUserDefaults
/**
* get UDID in NSUserDefaults
*
* @return UDID
*/
+ (NSString *)UDIDInUserDefaults {
return [[NSUserDefaults standardUserDefaults] objectForKey:kUDIDKey];
}
/**
* save UDID to NSUserDefaults
*
* @param udid
*/
+ (void)saveUDIDToUserDefaults:(NSString *)udid {
[[NSUserDefaults standardUserDefaults] setObject:udid forKey:kUDIDKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
/**
* save UDID to cache: keychain, pasteboard, userDefaults
*/
+ (void)save:(NSString *)udid {
if (DL_IS_STR_NIL(udid))
{
return;
}
[[self class] saveUDIDToKeychain:udid];
[[self class] saveUDIDToPasteboard:udid];
[[self class] saveUDIDToUserDefaults:udid];
}
/**
* get UDID
*
* @return UDID
*/
+ (NSString *)value {
// get exists value
if (DL_IS_STR_NIL(_udid)) _udid = [[self class] UDIDInKeychain];
if (DL_IS_STR_NIL(_udid)) _udid = [[self class] UDIDInPasteboard];
if (DL_IS_STR_NIL(_udid)) _udid = [[self class] UDIDInUserDefaults];
// generate new value
if (DL_IS_STR_NIL(_udid)) _udid = [[self class] appleIDFA];
if (DL_IS_STR_NIL(_udid)) _udid = [[self class] appleIDFV];
if (DL_IS_STR_NIL(_udid)) _udid = [[self class] randomUDID];
// 去掉字符中的'-'
_udid = [_udid stringByReplacingOccurrencesOfString:@"-" withString:@""];
[[self class] save:_udid];
return _udid;
}
@end
完整demo地址:
https://github.com/DaliZh/DLUDID
参考了两个比较好的网站:
https://blog.onliquid.com/persistent-device-unique-identifier-ios-keychain/
http://www.cocoachina.com/industry/20130422/6040.html