Henry的博客

有理想的人,生活总是火热的

欢迎来到Henry的博客,希望与您在iOS开发领域共同交流与学习


IOS开发-获取唯一设备标识的方法

在开发过程中,我们经常会被要求获取每个设备的唯一标识,以便后台做相应的处理。
我们来看看有哪些方法来获取设备的唯一标示,然后再分析下这些方法的利弊。具体可分为以下几种:
1、UDID
2、UUID
3、IDFA
4、IDFV
4、MAC(已经被禁用,除了5.0以前,哪位大神还在用此等上古神器)

一、UDID

什么是UDID

UDID 「Unique Device Identifier Description」是由子母和数字组成的40个字符串的序号,用来区别每一个唯一的iOS设备,包括 iPhones, iPads, 以及 iPod touches,这些编码看起来是随机的,实际上是跟硬件设备特点相联系的,另外你可以到iTunes,pp助手或itools等软件查看你的udid(设备标识)。

如下图所示:

UDID是用来干什么的?

UDID可以关联其它各种数据到相关设备上。例如,连接到开发者账号,可以允许在发布前让设备安装或测试应用;也可以让开发者获得iOS测试版进行体验。苹果用UDID连接到苹果的ID,这些设备可以自动下载和安装从App Store购买的应用、保存从iTunes购买的音乐、帮助苹果发送推送通知、即时消息。 在iOS 应用早期,UDID被第三方应用开发者和网络广告商用来收集用户数据,可以用来关联地址、记录应用使用习惯……以便推送精准广告

大多数应用都会用到苹果设备的UDID号,UDID通常有以下两种用途:

* 用于一些统计与分析目的;【第三方统计工具如友盟,广告商如ADMOB等。

* 将UDID作为用户ID来唯一识别用户,省去用户名,密码等注册过程。

为什么苹果反对开发人员使用UDID?

iOS 2.0版本以后UIDevice提供一个获取设备唯一标识符的方法uniqueIdentifier,通过该方法我们可以获取设备的序列号,这个也是目前为止唯一可以确认唯一的标示符。 许多开发者把UDID跟用户的真实姓名、密码、住址、其它数据关联起来;网络窥探者会从多个应用收集这些数据,然后顺藤摸瓜得到这个人的许多隐私数据。同时大部分应用确实在频繁传输UDID和私人信息。 为了避免集体诉讼,苹果最终决定在iOS 5 的时候,将这一惯例废除,开发者被引导生成一个唯一的标识符,只能检测应用程序,其他的信息不提供。现在应用试图获取UDID已被禁止且不允许上架
所以这个方法作废。

1
2
//UDID , 已废除
NSString *udid = [[UIDevice currentDevice] uniqueIdentifier];

二、UUID

什么是UUID(Universally Unique Identifier)

UUID是Universally Unique Identifier的缩写,中文意思是通用唯一识别码。它是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。这样,每个人都可以建立不与其它人冲突的 UUID。在此情况下,就不需考虑数据库建立时的名称重复问题。苹果公司建议使用UUID为应用生成唯一标识字符串。

获得的UUID值系统没有存储, 而且每次调用得到UUID,系统都会返回一个新的唯一标示符。如果你希望存储这个标示符,那么需要自己将其存储到NSUserDefaults, Keychain, Pasteboard或其它地方。目前大部分应用存的是UUID,它是苹果再iOS6后提供的一个获取大随机数的方法。UUID, 全球独立标识(Globally Unique Identifier),据wiki说UUID随机数算法得到的数重复概率为170亿分之一,170亿分之一什么概念?可以告诉你买一注双色球的中奖概率是1700万分之一。

  • CFUUID

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

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

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

  • NSUUID

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

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

跟CFUUID一样,这个值系统也不会存储,每次调用的时候都会获得一个新的唯一标示符。如果要存储的话,你需要自己存储。在我读取NSUUID时,注意到获取到的这个值跟CFUUID完全一样(不过也可能不一样)

三、IDFA(identifierForIdentifier)

什么是IDFA

广告标示符,在同一个设备上的所有App都会取到相同的值,是苹果专门给各广告提供商用来追踪用户而设的。但好在Apple默认是允许追踪的,而且一般用户都不知道有这么个设置,所以基本上用来监测推广效果,是戳戳有余了。
它是iOS 6中另外一个新的方法,提供了一个方法advertisingIdentifier,通过调用该方法会返回一个NSUUID实例,最后可以获得一个UUID,由系统存储着的。

1
2
import <AdSupport/AdSupport.h>
NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];

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

注意:Appstore禁止不使用广告而采集IDFA的app上架。

四、IDFV(identifierForVendor)

什么是IDFV

Vendor标示符,是给Vendor标识用户用的,每个设备在所属同一个Vender的应用里,都有相同的值。其中的Vender是指应用提供商,但准确点说,是通过BundleID的反转的前两部分进行匹配,如果相同就是同一个Vender,例如对于com.taobao.app1, com.taobao.app2 这两个BundleID来说,就属于同一个Vender,共享同一个IDFV的值。和IDFA不同的是,IDFV的值是一定能取到的,所以非常适合于作为内部用户行为分析的主id,来标识用户,替代OpenUDID。
它是iOS 6中新增的,跟advertisingIdentifier一样,该方法返回的是一个 NSUUID对象,可以获得一个UUID。如果满足条件“相同的一个程序里面-相同的vendor-相同的设备”,那么获取到的这个属性值就不会变。如果是“相同的程序-相同的设备-不同的vendor,或者是相同的程序-不同的设备-无论是否相同的vendor”这样的情况,那么这个值是不会相同的。

1
NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
但是如果用户将属于此Vender的所有App卸载,则IDFV的值会被重置,即再重装此Vender的App,IDFV的值和之前不同。

五、MAC Address

什么是MAC

MAC(Medium/Media Access Control)地址,用来表示互联网上每一个站点的标识符,采用十六进制数表示,共六个字节(48位)。其中,前三个字节是由IEEE的注册管理机构 RA负责给不同厂家分配的代码(高位24位),也称为“编制上唯一的标识符” (Organizationally Unique Identifier),后三个字节(低位24位)由各厂家自行指派给生产的适配器接口,称为扩展标识符(唯一性)。

MAC地址在网络上用来区分设备的唯一性,接入网络的设备都有一个MAC地址,他们肯定都是不同的,是唯一的。一部iPhone上可能有多个MAC地址,包括WIFI的、SIM的等,但是iTouch和iPad上就有一个WIFI的,因此只需获取WIFI的MAC地址就好了,也就是en0的地址。
形象的说,MAC地址就如同我们身份证上的身份证号码,具有全球唯一性。这样就可以非常好的标识设备唯一性,类似与苹果设备的UDID号,通常的用途有:
1)用于一些统计与分析目的,利用用户的操作习惯和数据更好的规划产品;
2)作为用户ID来唯一识别用户,可以用游客身份使用app又能在服务器端保存相应的信息,省去用户名、密码等注册过程。

如何使用Mac地址生成设备的唯一标识呢?

主要分三种:

1、直接使用“MAC Address” 
2、使用“MD5(MAC Address)” 
3、使用“MD5(Mac Address+bundle_id)”获得“机器+应用”的唯一标识(bundle_id 是应用的唯一标识)

iOS7之前,因为Mac地址是唯一的, 一般app开发者会采取第3种方式来识别安装对应app的设备。为什么会使用它?在iOS5之前,都是使用UDID的,后来被禁用。苹果推荐使用UUID 但是也有诸多问题,从而使用MAC地址。而MAC地址跟UDID一样,存在隐私问题,现在苹果新发布的iOS7上,如果请求Mac地址都会返回一个固定值,那么Mac Address+bundle_id这个值大家的设备都变成一致的啦,跟UDID一样相当于被禁用, 所以Mac Address 是不能够被使用为获取设备唯一标识的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_dl.h>
int mib[6];
size_t len;
char *buf;
unsigned char *ptr;
struct if_msghdr *ifm;
struct sockaddr_dl *sdl;
mib[0] = CTL_NET;
mib[1] = AF_ROUTE;
mib[2] = 0;
mib[3] = AF_LINK;
mib[4] = NET_RT_IFLIST;
if ((mib[5] = if_nametoindex("en0")) == 0) {
printf("Error: if_nametoindex error\n");
return NULL;
}
if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
printf("Error: sysctl, take 1\n");
return NULL;
}
if ((buf = malloc(len)) == NULL) {
printf("Could not allocate memory. error!\n");
return NULL;
}
if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
printf("Error: sysctl, take 2");
free(buf);
return NULL;
}
ifm = (struct if_msghdr *)buf;
sdl = (struct sockaddr_dl *)(ifm + 1);
ptr = (unsigned char *)LLADDR(sdl);
NSString *macStr = [NSString stringWithFormat:@"%02X:%02X:%02X:%02X:%02X:%02X",*ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];
free(buf);

总结

说了这么多, 才发现原来没有一种方法是可行的。没错, 其实自从苹果废除UDID后, 就不能达到获取设备真正的唯一标识了。因为这些方法中导致获取的唯一标示产生改变的原因, 或是重新调用方法, 或是重启设备, 或是卸载应用, 或是还原某些标识, 或者刷新系统…
所以, 不能达到从根本上获取唯一标识, 我们只能做到尽可能接近。下面是我用过的方法。

如何正确的获取设备的唯一标识

我用的方法是将获取的UUID永久存储在设备的KeyChain中, 这个方法在应用第一次启动时, 将获取的UUID存储进KeyChain中, 每次取的时候, 检查本地钥匙串中有没有, 如果没有则需要将获取的UUID存储进去。当你重启设备, 卸载应用再次安装,都不影响, 只是当设备刷机时, KeyChain会清空, 才会消失, 才会失效。
不只是这一种方法, 你也可以保存除UUID之外,其他合适的标识, 但利用KeyChain去存储标识的方式应该是最接近的。

重点:利用keyChain和UUID永久获得设备的唯一标识

什么是keychain

苹果增加更安全的存储方式:Keychain(相对于NSUserDefaults数据以明文的形式保存在)
提供了一种安全的保存私密信息(密码,序列号,证书等)的方式,
每个iOS程序都有一个独立的keychain存储,将数据加密后存储在本地,更安全.
当你删除APP后Keychain存储的数据不会删除,所以在重装App后,Keychain里的数据还能使用。
从ios 3.0开始,跨程序分享keychain变得可行而NSUserDefaults存储的数据会随着APP而删掉

话不多说,下面看看怎么用keychain方式存储UUID,达到删除应用重新安装后获取的UUID不会变(但系统reset后keychain存储的数据会消失。)
使用keychain请不要忘记引入Security包,引入头文件 <Security/Security.h>

定义一个工具类用户存储用户数据,提供存、取、删三个接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//
// MXKeyChain.h
// keyChainDemo
//
// Created by Henry on 2017/3/4.
// Copyright © 2017年 无届网络科技. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <Security/Security.h>
@interface MXKeyChain : NSObject
/**
* 存储字符串到 KeyChain
*/
+ (void)keyChainSaveString:(NSString *)string WithSecureKey:(NSString *)secureKey;
/**
* 从 KeyChain 中读取存储的字符串
*/
+ (NSString *)keyChainLoadStringWithSecureKey:(NSString *)secureKey;
/**
* 删除 KeyChain 信息
*/
+ (void)keyChainDeleteStringWithSecureKey:(NSString *)secureKey;
@end

用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//
// ViewController.m
// keyChainDemo
//
// Created by Henry on 2017/3/3.
// Copyright © 2017年 无届网络科技. All rights reserved.
#import "ViewController.h"
#import "MXKeyChain.h"
static NSString *const UUIDKEY = @"UUIDKEY";
@interface ViewController ()
- (IBAction)loadUUID;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self loadUUID];
}
//每次卸载应用重装加载的UUID都不会变,但重置系统后就没有数据了。
- (IBAction)loadUUID {
NSString *uuidStr = [MXKeyChain keyChainLoadStringWithSecureKey:UUIDKEY];
if (!uuidStr.length){
uuidStr = [[NSUUID UUID] UUIDString];//此方法每次加载的UUID都不一样
[MXKeyChain keyChainSaveString:uuidStr WithSecureKey:UUIDKEY];
}
NSLog(@"UUID: %@",uuidStr);
}
@end

本次演示个人用的demo

在项目中,导入MXKeyChain.h MXKeyChain.m调用即可

demo地址

最近的文章

iOS开发-Safari调试WebView页面

在iOS开发过程中,难免会加入html5页面来实现文章详情等等类似功能。我们都知道火狐等PC浏览器有类似firebug,审查元素等工具来调试网页样式与脚本,查看请求参数与请求头等等。在iOS开发中,这些网页检查器功能也是存在的(无论是模拟器还是真机),我们需要用到Mac自带的浏览器Safari。所以 …

于  iOS开发 继续阅读
更早的文章

iOS之UI-CAShapeLayer

内容大纲:1. CAShapeLayer简介 2. 贝塞尔曲线与CAShapeLayer的关系 3. strokeStart和strokeEnd 动画 4. 用CAShapeLayer实现进度条效果,以及更加复杂的效果 1.CAShapeLayer简介 CAShapeLayer继承自CALayer, …

于  iOS开发 继续阅读