iOS获取UDID问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zgsxzdl/article/details/51356129

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


首先介绍几个知识点:

1、CFUUID

从iOS2.0开始,CFUUID就已经出现了。它是CoreFoundatio包的一部分,因此API属于C语言风格。 CFUUIDCreate 方法用来创建CFUUIDRef,并且可以获得一个相应的NSString,如下代码:

CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);NSString *cfuuidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, cfuuid));

 

获得的这个CFUUID值系统并没有存储。每次调用CFUUIDCreate,系统都会返回一个新的唯一标示符。如果你希望存储这个标示符,那么需要自己将其存储到NSUserDefaults, Keychain, Pasteboard或其它地方。


2、NSUUID

NSUUID在iOS 6中才出现,这跟CFUUID几乎完全一样,只不过它是Objective-C接口。+ (id)UUID 是一个类方法,调用该方法可以获得一个UUID。通过下面的代码可以获得一个UUID字符串:

NSString *uuid = [[NSUUID UUID] UUIDString];

 

跟CFUUID一样,这个值系统也不会存储,每次调用的时候都会获得一个新的唯一标示符。如果要存储的话,你需要自己存储。


3、IDFA(广告标示符)

这是iOS 6中另外一个新的方法,advertisingIdentifier是新框架AdSupport.framework的一部分。ASIdentifierManager单例提供了一个方法advertisingIdentifier,通过调用该方法会返回一个上面提到的NSUUID实例。

NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];


广告标示符是由系统存储着的。不过即使这是由系统存储的,但是有几种情况下,会重新生成广告标示符。如果用户完全重置系统((设置程序 -> 通用 -> 还原 -> 还原位置与隐私) ,这个广告标示符会重新生成。另外如果用户明确的还原广告(设置程序-> 通用 -> 关于本机 -> 广告 -> 还原广告标示符) ,那么广告标示符也会重新生成。关于广告标示符的还原,有一点需要注意:如果程序在后台运行,此时用户“还原广告标示符”,然后再回到程序中,此时获取广告标示符并不会立即获得还原后的标示符。必须要终止程序,然后再重新启动程序,才能获得还原后的广告标示符。之所以会这样,我猜测是由于ASIdentifierManager是一个单例。


4、IDFV(Vindor标示符

这种叫法也是在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会被重置。


5、UDID 设备唯一标识(已被禁)

在之前的版本中是可用的,但是在iOS5以及之后的版本中,以及被弃用了。虽然,这个UDID用得很广泛,但是,不得不说的是,它在慢慢的远离开发者,不能在考虑使用UDID了。至于这个标示符是转为私有方法,或者完全从以后的iOS版本中移除,还有待观察。不过,这个UDID在部署企业级签名程序时,非常方便。获取UDID的方法如下:

NSString *udid = [[UIDevice currentDevice] uniqueIdentifier];


6、OpenUDID

在iOS 5发布时,uniqueIdentifier被弃用了,这引起了广大开发者需要寻找一个可以替代UDID,并且不受苹果控制的方案。由此 OpenUDID成为了当时使用最广泛的开源UDID替代方案。OpenUDID在工程中实现起来非常简单,并且还支持一系列的广告提供商。

NSString *openUDID = [OpenUDID value];

 

OpenUDID利用了一个非常巧妙的方法在不同程序间存储标示符 — 在粘贴板中用了一个特殊的名称来存储标示符。通过这种方法,别的程序(同样使用了OpenUDID)知道去什么地方获取已经生成的标示符(而不用再生成一个新的)。是不是感觉这个就是你想要的,但是,UIPasteboard由共享变为沙盒化了,只对相同的app group可见。所以现在OpenUDID基本只能保证同一个APP的openUDID一样了


7、UIPasteboard 剪切板

可以自定义APP的剪切板

8、Keychain 钥匙串

我们可以把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 <UIKit/UIDevice.h>
#import <UIKit/UIPasteboard.h>
#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


猜你喜欢

转载自blog.csdn.net/zgsxzdl/article/details/51356129