Tech. Memo

stay hungry, stay foolish.


  • 首页

  • 归档

iOS唯一ID:UDID,UUID,advertisingIdentifier,identifierForVendor,KeyChain。

发表于 2016-02-02   |   分类于 iOS   |  

文章出处,原创于 https://HawkingOuYang.github.io/

我的GitHub


© OYXJ

iOS唯一ID:UDID,UUID,Mac地址,advertisingIdentifier,identifierForVendor,KeyChain。

设备唯一 id

iOS 标识 设备id ?→ FCUUID

为什么,使用 FCUUID, 因为:

1
2
3
4
5
/*
Method [[UIDevice currentDevice] uniqueIdentifier] is not allowed any more
http://stackoverflow.com/questions/9396187/method-uidevice-currentdevice-uniqueidentifier-is-not-allowed-any-more-i-ne
*/
[[UIDevice currentDevice] uniqueIdentifier];
1
2
3
4
5
6
7
8
/*
* iOS 6.0
* use wifi's mac address
*/
+ (NSString*)_UDID_iOS6
{
return [SvUDIDTools getMacAddress];
}
1
2
3
4
5
6
7
8
9
10
11
/*
* iOS 7.0
* Starting from iOS 7, the system always returns the value 02:00:00:00:00:00
* when you ask for the MAC address on any device.
* use identifierForVendor + keyChain
* make sure UDID consistency atfer app delete and reinstall
*/
+ (NSString*)_UDID_iOS7
{
return [[UIDevice currentDevice].identifierForVendor UUIDString];
}

但是

1
[[UIDevice currentDevice].identifierForVendor UUIDString];

identifierForVendor 不靠谱,因为:
app被用户删掉之后,identifierForVendor会变化。

advertisingIdentifier 也不行,因为:
app中使用 advertisingIdentifier,导入 AdSupport.framework, 但实际上“app的界面中看不到广告”,那么,AppStore审核时 被拒绝!

所以,用 FCUUID
请相信开源的力量,并且我亲测有效(也请相信我),看:

1
2
3
4
5
6
7
8
9
10
/**
* uuidForDevice PERSISTS for: System reboot、System upgrade、System reset。
* @attention System reset (persists only if the user restores a device backup which includes also keychain's data)
*
* @return uuidForDevice
*/
+ (NSString *_Nonnull)uuidForDevice
{
return [FCUUID uuidForDevice];
}

UDID

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
/*! @brief 生成一个唯一id
*
* @attention 可以用于本地数据库 实体的ID(即主键 like the primary key in the database)
*
* @note UUID (Universally Unique Identifier): A sequence of 128 bits that can guarantee uniqueness across space and time, defined by RFC 4122.
*
* @return 唯一id字符串,形如:E621E1F8-C36C-495A-93FC-0C247A3E6E5F
*/
+ (NSString *_Nonnull)generateUUID
{
/**
http://nshipster.com/uuid-udid-unique-identifier/
http://stackoverflow.com/questions/21872233/differences-between-udid-and-uuid
UUID (Universally Unique Identifier): A sequence of 128 bits that can guarantee uniqueness across space and time, defined by RFC 4122.
*/
CFUUIDRef identifier = CFUUIDCreate(NULL);
CFStringRef identifierStringRef = CFUUIDCreateString(NULL, identifier);
CFRelease(identifier);
NSString *identifierString = (__bridge NSString *)identifierStringRef;
NSString *uuid = [NSString stringWithString:identifierString];
CFRelease(identifierStringRef);
return uuid;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*! @brief 生成一个唯一id
*
* @note GUID (Globally Unique Identifier): Microsoft’s implementation of the UUID specification; often used interchangeably with UUID.
*
* @return 唯一id字符串,相比于:E621E1F8-C36C-495A-93FC-0C247A3E6E5F
* 但是去除"-",形如:E621E1F8C36C495A93FC0C247A3E6E5F
*/
+ (NSString *_Nonnull)createGUID
{
/**
http://nshipster.com/uuid-udid-unique-identifier/
GUID (Globally Unique Identifier): Microsoft’s implementation of the UUID specification; often used interchangeably with UUID.
*/
NSMutableString *guid = (NSMutableString *)[[NSUUID UUID] UUIDString];
return [guid stringByReplacingOccurrencesOfString:@"-" withString:@""];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 根据时间(精确到毫秒) 生成唯一id (这个不是 “跨设备唯一”的,特别注意)
*
* @return 根据时间(精确到毫秒) 生成唯一id,形如:yyyyMMddHHmmssSSS98 其中98是随机数字
*/
+ (NSString *_Nonnull)locationTimeID
{
NSDateFormatter *dateToStrFormatter = [[NSDateFormatter alloc] init];
[dateToStrFormatter setDateFormat:@"yyyyMMddHHmmssSSS"];
NSString *strDate = [dateToStrFormatter stringFromDate:[NSDate date]];
return [NSString stringWithFormat:@"%@%d", strDate, (arc4random() % 100)];
}
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
/**
* 当前app的bundleID。
*
* @note 自动判断是否 通配符bundleID,若是,则会处理: 在最后拼接[ "_%@", app名字全拼 ],形如‘软件1’:com.example.*_ruanjian1
*
* @return 当前app的bundleID,自动判断是否 通配符bundleID,若是,则会处理 在最后拼接[ "_%@", app全拼 ]
*/
+ (NSString *_Nonnull)appBundleIdentifier
{
static NSString *bundleID = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSBundle *bundle = [NSBundle mainBundle];
bundleID = [bundle bundleIdentifier];
NSString *lastStr = [[bundleID componentsSeparatedByString:@"."] lastObject];
if ([lastStr stringByReplacingOccurrencesOfString:@"*" withString:@""].length == 0) {
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSString *appName = [infoDictionary objectForKey:@"CFBundleDisplayName"];
if(appName.length <= 0)
appName = @"software";
NSString *appNameFullSpell = [appName pinyinWithoutBlank];
bundleID = [bundleID stringByAppendingFormat:@"_%@", appNameFullSpell];
}
}); //<---- dispatch_once END ---->//
return bundleID;
}
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
* uuidForDevice PERSISTS for: System reboot、System upgrade、System reset。
* @attention System reset (persists only if the user restores a device backup which includes also keychain's data)
*
* @return uuidForDevice
*/
+ (NSString *_Nonnull)uuidForDevice
{
DDLogInfo(@"%@,%@", THIS_FILE,THIS_METHOD);
static NSMutableString *uuidForDeviceAsIs = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//! 形如 @"C3A53BB7-8F1A-4372-A37E-1F12FCA77A3D"
NSString *uuidForVendor = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
DDLogInfo(@"%@,%@", @"uuidForVendor",uuidForVendor);
/**
但是,uuidForVendor 用作 设备唯一ID,不够准确,所以使用开源库,[FCUUID uuidForDevice];
形如 @"59af2b707b60455f8ed88d1414c233ea"
1、这个开源库,变成“小写”了,所以要先还原"大写"
2、这个开源库,把"-"删除了,所以要先还原"-"
*/
//! Ref.: https://github.com/fabiocaccamo/FCUUID
NSString *uuidForDevice = [FCUUID uuidForDevice];
DDLogInfo(@"%@,%@", @"uuidForDevice",uuidForDevice);
//! 分隔符 ‘-’
NSString * const separatorStr = @"-";
// 找到"-"的位置
NSMutableArray<NSValue *> *rangeMArr = [NSMutableArray array];
NSRange range = [uuidForVendor rangeOfString:separatorStr options:NSLiteralSearch range:NSMakeRange(0, uuidForVendor.length)];
while (range.location != NSNotFound) {
[rangeMArr addObject:[NSValue valueWithRange:range]];
range = [uuidForVendor rangeOfString:separatorStr
options:NSLiteralSearch
range:NSMakeRange(range.location+range.length,
uuidForVendor.length-range.location-range.length)
];
}
DDLogInfo(@"%@,%@,%@", @"range of ", separatorStr, rangeMArr);
// 1、这个开源库,变成“小写”了,所以要先还原"大写"
uuidForDeviceAsIs = [[uuidForDevice uppercaseString] mutableCopy];
/**
2、这个开源库,把"-"删除了,所以要先还原"-"
[FCUUID uuidForDevice]; 这个开源库,把"-"删除了,所以要先还原"-"
*/
if (uuidForVendor.length - rangeMArr.count == uuidForDevice.length) {//除去'-',uuidForVendor 和 uuidForDevice 长度 相等
[rangeMArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSValue *rangeValue = obj;
NSRange range = [rangeValue rangeValue];
[uuidForDeviceAsIs insertString:separatorStr atIndex:range.location];
}];
DDLogInfo(@"%@,%@", @"uuidForDeviceAsIs",uuidForDeviceAsIs);
}else{
DDLogError(@"%@,%@", @"uuidForDevice 错误!",@"除去'-',uuidForVendor 和 uuidForDevice 长度 不 相等,直接使用 uuidForDevice");
DDLogInfo(@"%@,%@", @"uuidForDeviceAsIs", uuidForDeviceAsIs);
}
}); //<---- dispatch_once END ---->//
DDLogInfo(@"%@,%@", @"uuidForDeviceAsIs", uuidForDeviceAsIs);
return uuidForDeviceAsIs;
}

advertisingIdentifier

第一部分:问题描述

概述:使用广告id(即 idfa),但是app内没有出现广告,导致 AppStore审核 被拒绝。

详述:

PLA 3.3.12 详情

我们发现,您的 app 使用 iOS 广告 ID,但并未包含广告功能。根据 App Store 审核准则 的规定,这种行为不符合 iOS Developer Program 许可协议。

具体而言,iOS Developer Program 许可协议的第 3.3.12 部分规定:

“您和您的应用软件(以及与您签订广告投放合同的任意第三方)可以使用广告 ID,并且通过广告 ID 获取的任何信息只能用于广告投放用途。如果用户重置广告 ID,那么您必须同意不通过直接或间接的方式将先前的广告 ID 及其任何相关信息与重置的广告 ID 进行组合、联合、链接或以其他方式关联起来。”

注:iAd 不使用 AdSupport 框架、ASIdentifierManager 或广告 ID。因此,无需为 iAd 实施这些功能,也不得出于 iAd 支持目的在您的 app 中添加这些功能。

如果您的 app 正在投放广告,请:

  • 确保您已在设备(而不只是模拟器)上测试您的 app,并确保您在测试前已移除您 app 的所有旧版本

  • 告知我们如何才能在您的 app 中找到广告

如果您的 app 没有投放广告,请检查您的代码(包括所有第三方库),以移除以下对象的所有实例:

类: ASIdentifierManager
选择器: advertisingIdentifier
框架: AdSupport.framework

如果您计划在未来版本中加入广告,那么在您加入广告功能之前,请从您的 app 中移除广告 ID。

若要查找广告 ID,请使用“nm”工具。有关“nm”工具的信息,请参阅 nm 手册页 。

如果您没有库源的访问权限,则可以使用“strings”或“otool”命令行工具搜索编译的二进制文件。“strings”工具列出了库调用的方法,且“otool -ov”列出了 Objective-C 类结构及其定义的方法。这些技术可以帮助您缩小存在问题的代码的查找范围。

第二部分:问题场景

当时场景:

  1. libWeiboSDK.a 使用到了 advertisingIdentifier(即 广告id)
    libWoPushSDK.a 使用到了 advertisingIdentifier(即 广告id)

备注:
如何找出使用了 idfa 的 .a 文件 ?
App store 审核上传Advertising Identifier查找和IDFA
http://blog.csdn.net/folish_audi/article/details/33696609

提交之前选项:此App是否使用广告标示符(IDFA)? ---- 选择了“否”。导致无法提交,如图:

  1. 所以,此App是否使用广告标示符(IDFA)? ---- 选择了“是”。可以提交,进行审核。

加一个AD广告的SDK到工程,但是不使用。

然后提交时 选择在app内投放广告

同时选择 “本人确认在此确认”

但是,这么做,AppStore审核 被拒绝了。
但是,这么做,AppStore审核 被拒绝了。
但是,这么做,AppStore审核 被拒绝了。

详见:
第一部分:问题描述
概述:使用广告id(即 idfa),但是app内没有出现广告,导致 AppStore审核 被拒绝。

第三部分:问题解决

关于手机的序列号,UDID,IMEI,IMSI,ICCID详解
http://ju.outofmemory.cn/entry/64228

iOS唯一标示
http://milker90.blog.163.com/blog/static/229430038201441643813286/

核心信息:

idfa

全名:advertisingIdentifier
代码:
NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];

来源:iOS6.0及以后
说明:直译就是广告id, 在同一个设备上的所有App都会取到相同的值,是苹果专门给各广告提供商用来追踪用户而设的,用户可以在 设置|隐私|广告追踪 里重置此id的值,或限制此id的使用,故此id有可能会取不到值,但好在Apple默认是允许追踪的,而且一般用户都不知道有这么个设置,所以基本上用来监测推广效果,是戳戳有余了。
注意:由于idfa会出现取不到的情况,故绝不可以作为业务分析的主id,来识别用户。


identifierForVendor

idfv

全名:identifierForVendor
代码:
NSString *idfv = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
来源:iOS6.0及以后
说明:顾名思义,是给Vendor标识用户用的,每个设备在所属同一个Vender的应用里,都有相同的值。其中的Vender是指应用提供商,但准确点说,是通过BundleID的反转的前两部分进行匹配,如果相同就是同一个Vender,例如对于com.taobao.app1, com.taobao.app2 这两个BundleID来说,就属于同一个Vender,共享同一个idfv的值。和idfa不同的是,idfv的值是一定能取到的,所以非常适合于作为内部用户行为分析的主id,来标识用户,替代OpenUDID。
注意:如果用户将属于此Vender的所有App卸载,则idfv的值会被重置,即再重装此Vender的App,idfv的值和之前不同。

解决办法:

  1. 对于 微博SDK(即 libWeiboSDK.a), 尝试从 新浪微博开放平台 找“不包含idfa的SDK”,但是没有找到。
  2. 对于 沃推SDK(即 libWoPushSDK.a),协调 亚信(写 沃推SDK的公司),把 idfa 换成 idfv。

去掉,微博分享的功能,
删除 对 微博SDK 在项目中的引用。

然后,在提交审核的时候,此App是否使用广告标示符(IDFA)? ---- 选择了“否”。可以提交,进行审核。

并发起 “加急审核”。

半天之后(加急审核,速度真快),审核通过,如图:

其中,加急审核的核心理由是:奔溃,数据错乱。

然后选择,手动上架,一个小时之后,就可以在AppStore下载 最新版本了。

第四部分:其他尝试

根据前面 三个部分,进行第四个部分的“其他尝试”---

6.0.3.8 这个版本,被AppStore拒绝了。

换过了 WoPush的SDK,使之 不包含 广告id,即 idfa。
但是,新浪微博的SDK,没有找到 不包含 广告id 的SDK。

为了避免:

(1)实际上 “使用到了 广告id”,在提交审核的时候,
但是勾选 “没有使用 广告id”,导致 无法 提交审核。

(2)勾选 “使用到了 广告id”,Apple真正来审核了,
软件中 “并没有出现广告”, 导致 审核 被拒绝。

所以,现在有三种选择:

  1. 微博分享,这个功能,先去掉。删除 微博的SDK。
  2. 微博分享,换成 使用 友盟分享,来实现。
  3. 加入广告功能。

其中:

  1. 工作量最小,风险最小,能最快地再次提交审核。
  2. 工作量稍大,需要时间。
  3. 工作量最大,风险最大。

但是:
选择3 是 不可避免的,因为以后要做 “使用广告id 自动登录”。

我的建议是,目前选1.
等待 AppStore审核 通过之后,选择3. ----- 也可以尝试,使用 identifierForVendor 代替 advertisingIdentifier,那么 3 就不需要做。

第五部分:最终方案

考虑到:在用户删除所有 该开发商的app之后,identifierForVendor 会改变。
那么:使用 keyChain,更合适。

这部分由ZhangJianMin 实现的。请看 沃邮箱项目中的文件:DeviceIdUtils.h 和 DeviceIdUtils.m
/*
iOS的keyChain是一个相对独立的空间,当程序替换,删除时并不会删除keyChain的内容。
刷机,恢复出厂应该就没有了。

一键注册,暂时使用keyChain中保存的设备id
keyChain中保存的设备id值是首次安装沃邮箱时产生的供应商id
*/

后来,我在GitHub上找了一个,实现原理相同,但是更好的 FCUUID

KeyChain。

1
2
3
# 钥匙串
pod 'UICKeyChainStore'
pod 'SSKeychain'
1…343536…43
HawkingOuYang

HawkingOuYang

WeApp Web iOS

43 日志
10 分类
10 标签
RSS
GitHub StackOverflow Email
Creative Commons
© 2015 - 2019 HawkingOuYang
由 Hexo 强力驱动
主题 - NexT.Mist
Hosted by Coding Pages