唯一的设备ID

1. UDID与设备ID
   UDID的全称是Unique Device Identifier,它是iOS设备的唯一标识码,由40位十六进制的字母和数字组成。在iOS5以下,苹果的开发者们通常使用UDID作为设备的唯一标识,我们可以通过调用私有API的方式获得UDID,代码如下:
     NSString *udid = [[UIDevice currentDevice]uniqueIdentifier];

除了通过调用私有的API获取UDID之外,还可以通过Safari浏览器安装描述文件来获取UDID,访问http:www.exchen.net/udid,然后点击“获取UDID”按钮,会出现描述文件的安装提示,安装好描述文件以后,页面上会显示UDID。

 关于如何通过safari浏览器获取UDID,详情可以参阅https://www.exchen.net/通过-safari-浏览器获取-udid.html。

2. IDFA

  对于一个设备上的所有应用,获取的IDFA都是一样的。由于苹果取消了UDID的获取方法,IDFA就成了一种标识和追踪用户的常用方法。一台新的iOS设备在激活之后,IDFA是默认开启的,用户也可以关闭或者重置,如果重置,应用再次获取IDFA就会有变化,但是用户一般是不知道这个开关和设置的。IDFA的获取代码如下:

#import

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

NSLog(@”IDFA: %@”,strIDFA);

输出结果如下:
2020-04-20 15:33:46.831800+0800 DeviceUDID[1461:137700] IDFA: 3D42C428-513E-44EA-AB82-BC49DD641286

3.IDFV

IDFV是供应商标识符,当同一个证书签发的多个应用安装在同一个设备上时,这些应用获取的IDFV都是一样的。不论卸载了几个应用,只要设备上还存在该证书签发的应用,重新安装的证书签发的其他应用,IDFV就不会变。如果将该证书签发的应用全部卸载,重新安装的应用所获取的IDFV就会变化。获取IDFV的代码如下:

NSString *strIDFV = [UIDevice currentDevice]identifierForVendor]UUIDString];

NSLog(@”strIDFV:%@”,strIDFV);

运行后输出的结果:

2020-04-20 17:29:45.531076+0800 DeviceUDID[2356:199871] strIDFV: 0E07AAE2-3EAA-4011-846F-2793D10C7C1F

4.OpenUDID

OpenUDID是一种开源的ID生成算法,下载地址为:https://github.com/ylechelle/OpenUDID,使用OpenUDID的代码如下:

#import “OpenUDID.h”

NSString *strOpenUDID = [OpenUDID value];

NSLog(@”strOpenUDID:%@”,strOpenUDID);

打印数据:

由于高版本的Xcode默认启用ARC,而早期的OpenUDID出现的时候还没有ARC模式,所以编译时要注意,在Build Phases里的Compile Sources中,给OpenUUID.m添加-fno-objc-arc标记来关闭ARC,如图所示。

OpenUUID的核心原理是将生成的ID保存到剪切板中,同时在沙盒的Preferences目录下也保存一份,当应用被卸载时,沙盒目录会清空,但是剪切板中的数据还存在,所以从剪切板中获取数据OpenUUID就可以了。

下面我们来看一下OpenUUID内部实现的代码:

5. simulateIDFA

2016年,有米公司开源了一个获取ID的方法simulateIDFA,该方法可以在短时间内识别一台设备的唯一性,主要用于进行广告检测。由于一些设备的IDFA被关闭,所以simulateIDFA就可以成为一种“曲线救国”的方法。下载地址为: https://github.com/youmi/SimulateIDFA,调用方法也很简单,只需要调用createSimulateIDFA函数即可,代码如下:

#import “SimulateIDFA.h”
NSString *simulateIDFA = [SimulateIDFA createSimulateIDFA];

NSLog(@”simulateIDFA: %@”,simulateIDFA);
2020-04-21 10:03:46.321838+0800 DeviceUDID[17819:78121] simulateIDFA: 7AA3216A-905E-1F0B-9EE0-8CF164A6ABCA

createSimulateIDFA函数获取ID的原理:首先获取两组信息,一组是不稳定的信息,包括系统启用时间、国家代码、本地语言、设备名称,另一组是稳定的信息,包括系统版本、机型、运营商名称、内存、coreServices文件创建时间、硬盘使用空间;然后将这两组获取到的信息都格式化为字符串后放到fingerPrintUnstablePart和fingerPrintStablePart变量中;接着对这两组信息进行MD5加密,最后将加密后的两组值通过combineTwoFingerPrint函数转化为与IDFA一样的格式。SimulateIDFA的核心代码如下:

7. ID的持久存储
         
众所周知,每个iOS应用在系统上都对应一个沙盒,它只能在自己的沙盒中存储数据,如果应用被卸载,那么其沙盒目录也会被删除,就像微信被卸载了,那么本地的聊天记录肯定也就没有了。由于UDID不能获取,所以应用开发者只能自己生成一个ID,想要这个ID不变,就必须持久化的数据存储。在iOS系统上有两个地方可以做到应用被卸载后,数据不会被清理,一个是Keychain钥匙串,还有一个是剪切板。而之前我们所介绍的OpenUUID就是利用剪切板存储的。

    1. Keychain存储

  苹果封装了一个读写Keychain的对象keychainItemWrapper,将该对象的源代码文件添加到工程中。需要注意的是,在Build Phases里,在Compile Sources里的KeychainItemWrapper.m中,添加Compiler Flags为-fno-objc-arc来关闭ARC,如图所示

                                          KeychainItemWrapper.m关闭ARC模式

这里定义了一个函数getIDForKeychain,通过这个函数能获取和生成ID,代码如下:
NSString *keychainID = [self getIDForKeychain];

下面来看看getIDForKeychain函数的过程,其代码如下:
 

//获取和生成一个ID并用keychain进行存储

-(NSString *)getIDForKeychain{

    NSString *strBundleSeedID = [self bundleSeedID];

    NSDictionary *infoDic = [[NSBundle mainBundle]infoDictionary];

    NSString *strAppname = [infoDic objectForKey:@”CFBundleIdentifier”];

    NSString *strGroup = [NSString stringWithFormat:@”%@.%@”,strBundleSeedID,strAppname];

    NSLog(@”strGroup:%@”,strGroup);

    KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc]initWithIdentifier:@”super” accessGroup:strGroup];

    NSString *strValue = [keychainItem objectForKey:(NSString *)CFBridgingRelease(kSecValueData)];

    if ([strValue isEqualToString:@””]||strValue==nil) {

        //随机生成ID

        CFUUIDRef uuid = CFUUIDCreate(NULL);

        strValue = (NSString *)CFBridgingRelease(CFUUIDCreateString(NULL, uuid));

        strValue = [self md5:strValue];//MD5

        [keychainItem setObject:strValue forKey:(NSString *)CFBridgingRelease(kSecValueData)];

    }

    return strValue;

}
首先,要获取bundleSeedID,也就是证书前缀,将证书的前缀和应用的CFBundleIdentifier格式化为一个group.这个group很重要,因为应用之间读取keychain也是互相隔离的,必须要在同一个访问组才能读取。然后调用[keychainItem objectForkey]获取对应的keychain中的值。如果获取的数据为空,说明是第一次运行,需要生成ID,其方法是调用CFUUIDCreateString函数生成一个随机的UUID串,然后再进行一次MD5加密,接着调用[keychainItem setObject]将ID写入keychain,最后返回ID。

bundleSeedID获取证书的前缀的函数如下,其实际原理也是从keychain中读取:

-(NSString *)bundleSeedID{

    NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:

                           (NSString *)CFBridgingRelease(kSecClassGenericPassword),kSecClass,

                           @”bundleSeedID”,kSecAttrAccount,

                           @””,kSecAttrService,

                           (id)kCFBooleanTrue,kSecReturnAttributes,

                           nil];

    CFDictionaryRef result = nil;

    OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&result);

    if (status==errSecItemNotFound) {

        status = SecItemAdd((CFDictionaryRef)query, (CFTypeRef *)&result);

    }

    if (status!=errSecSuccess) {

        return nil;

    }

    NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:(NSString *)CFBridgingRelease(kSecAttrAccessGroup)];

    NSArray *compents = [accessGroup componentsSeparatedByString:@”.”];

    NSString *bundleSeedID = [[compents objectEnumerator]nextObject];

    CFRelease(result);

    return bundleSeedID;

}

2. 剪贴板
  除了Keychain能够进行持久化的数据存储外,剪贴板也可以。我们将ID保存在剪贴板中,如果应用被卸载了,下次安装依然能够获取到,并且在系统升级更新时也保持不变。下面定义了一个getIDForPasteboard函数,通过这个函数获取和生成ID。

NSString * pasteboardID = [self getIDForPasteboard];
下面我们来看看getIDForPasteboard函数的实现过程,代码如下:

-(NSString *)getIDForPasteboard{

    NSString *pasteboardName = @”exchen.net”;

    NSString *pasteboardType = @”id”;

    NSString *strID;

    UIPasteboard *pasteboard = [UIPasteboard pasteboardWithName:pasteboardName create:YES];

    pasteboard.persistent = YES;

    //从剪贴板里读取ID

    id item = [pasteboard dataForPasteboardType:pasteboardType];

    if (item) {

        //如果读取的数据不为空,说明之前就写入了ID

        item = [NSKeyedUnarchiver unarchiveObjectWithData:item];

        if (item!=nil) {

            NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:item];

            strID = [dic objectForKey:pasteboardType];

        }

    }

    else{

        //随机生成ID

        CFUUIDRef uuid = CFUUIDCreate(NULL);

        strID = (NSString *)CFBridgingRelease(CFUUIDCreateString(NULL, uuid));

        strID = [self md5:strValue];//MD5

        

        //写入剪贴板

        NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:item];

        [dic setValue:strID forKey:pasteboardType];

        [pasteboard setData:[NSKeyedArchiver archivedDataWithRootObject:dic] forPasteboardType:pasteboardType];

    }

    return strID;

}
首先,定义剪贴板的名称和类型,调用[UIPasteboard pasteboardWithName]创建剪贴板,通过dataForPasteboardType获取相应类型的数据。如果能够读到内容,就调用[NSKeyedUnarchiver unarchiveObjectWithData:item]将数据解析出来,然后返回ID;如果读不到内容,就说明是第一次运行,需要生成ID。生成ID的方法是调用CFUUIDCreateString创建一个随机的UUID,接着对UUID进行MD加密。ID生成之后,调用setData向剪贴板中写入数据,最后返回ID。

8. DeviceToken

DeviceToken主要用于推送消息,也可以识别用户的设备,其获取方法是在AppDeleagte.m里添加didRegisterForRemoteNotificationsWithDeviceToken方法,具体代码如下:
 

-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{

    NSString *strDeviceToken = [[[[deviceToken description]stringByReplacingOccurrencesOfString:@”” withString:@””]stringByReplacingOccurrencesOfString:@” ” withString:@””];

    NSLog(@”DeviceToken:%@”,strDeviceToken);

}

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年3月20日
下一篇 2020年3月20日

相关推荐