udid的全称是unique device identifier,它就是苹果ios设备的唯一识别码,它由40位16进制数的字母和数字组成(越狱的设备通过某些工具可以改变设备的udid)。移动网络可利用udid来识别移动设备,但是,从ios5.0(2011年8月份)开始,苹果宣布将不再支持用uniqueidentifier方法获取设备的udid,ios5以下是可以用的。苹果从ios5开始就移除了通过代码访问udid的权限。从2013年5月1日起,试图访问uidids的程序将不再被审核通过,替代的方案是开发者应该使用“在ios 6中介绍的vendor或advertising标示符”。所以udid是绝对是不能再使用了。
//uuid , 已废除 nsstring *udid = [[uidevice currentdevice] uniqueidentifier];
为什么苹果反对开发人员使用udid?
ios 2.0版本以后uidevice提供一个获取设备唯一标识符的方法uniqueidentifier,通过该方法我们可以获取设备的序列号,这个也是目前为止唯一可以确认唯一的标示符。 许多开发者把udid跟用户的真实姓名、密码、住址、其它数据关联起来;网络窥探者会从多个应用收集这些数据,然后顺藤摸瓜得到这个人的许多隐私数据。同时大部分应用确实在频繁传输udid和私人信息。 为了避免集体诉讼,苹果最终决定在ios 5 的时候,将这一惯例废除,开发者被引导生成一个唯一的标识符,只能检测应用程序,其他的信息不提供。现在应用试图获取udid已被禁止且不允许上架。
uuid是universally unique identifier的缩写,中文意思是通用唯一识别码。它是让分布式中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。这样,每个人都可以建立不与其它人冲突的 uuid。在此情况下,就不需考虑建立时的名称重复问题。苹果公司建议使用uuid为应用生成唯一标识字符串。
获得的uuid值系统没有存储, 而且每次调用得到uuid,系统都会返回一个新的唯一标示符。如果你希望存储这个标示符,那么需要自己将其存储到nsuserdefaults, keychain, pasteboard或其它地方。
从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或其它地方。
nsuuid在ios 6中才出现,这跟cfuuid几乎完全一样,只不过它是objective-c接口。+ (id)uuid 是一个类方法,调用该方法可以获得一个uuid。通过下面的代码可以获得一个uuid字符串:
nsstring *uuid = [[nsuuid uuid] uuidstring];
跟cfuuid一样,这个值系统也不会存储,每次调用的时候都会获得一个新的唯一标示符。如果要存储的话,你需要自己存储。在我读取nsuuid时,注意到获取到的这个值跟cfuuid完全一样(不过也可能不一样)
在ios 5发布时,uniqueidentifier被弃用了,这引起了广大开发者需要寻找一个可以替代udid,并且不受苹果控制的方案。由此openudid成为了当时使用最广泛的开源udid替代方案。openudid在工程中实现起来非常简单,并且还支持一系列的广告提供商。
openudid利用了一个非常巧妙的方法在不同程序间存储标示符 — 在粘贴板中用了一个特殊的名称来存储标示符。通过这种方法,别的程序(同样使用了openudid)知道去什么地方获取已经生成的标示符(而不用再生成一个新的)。而且根据贡献者的代码和方法,和一些开发者的经验,如果把使用了openudid方案的应用全部都删除,再重新获取openudid,此时的openudid就跟以前的不一样。可见,这种方法还是不保险。
但是openudid库早已经弃用了, 在其中也指明了, 停止维护openudid的原因是为了更好的向苹果的举措靠拢, 还指明了mac address不是一个好的选择。
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又能在服务器端保存相应的信息,省去用户名、密码等注册过程。
主要分三种:
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 是不能够被使用为获取设备唯一标识的。
广告标示符,在同一个设备上的所有app都会取到相同的值,是苹果专门给各广告提供商用来追踪用户而设的。但好在apple默认是允许追踪的,而且一般用户都不知道有这么个设置,所以基本上用来监测效果,是戳戳有余了。
它是ios 6中另外一个新的方法,提供了一个方法advertisingidentifier,通过调用该方法会返回一个nsuuid实例,最后可以获得一个uuid,由系统存储着的。
#import nsstring *adid = [[[asidentifiermanager sharedmanager] advertisingidentifier] uuidstring];
不过即使这是由系统存储的,但是有几种情况下,会重新生成广告标示符。如果用户完全重置系统((设置程序 -> 通用 -> 还原 -> 还原位置与隐私) ,这个广告标示符会重新生成。另外如果用户明确的还原广告(设置程序-> 通用 -> 关于本机 -> 广告 -> 还原广告标示符) ,那么广告标示符也会重新生成。
关于广告标示符的还原,有一点需要注意:如果程序在后台运行,此时用户“还原广告标示符”,然后再回到程序中,此时获取广 告标示符并不会立即获得还原后的标示符。必须要终止程序,然后再重新启动程序,才能获得还原后的广告标示符。
所以idfa也不可以作为获取唯一标识的方法,来识别用户。
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”这样的情况,那么这个值是不会相同的。
nsstring *stridfv = [[[uidevice currentdevice] identifierforvendor] uuidstring];
但是如果用户将属于此vender的所有app卸载,则idfv的值会被重置,即再重装此vender的app,idfv的值和之前不同。
推送token+bundle_id的方法:
1、应用中增加推送用来获取token
2、获取应用bundle_id
3、根据token+bundle_id进行散列运算
apple push token保证设备唯一,但必须有网络情况下才能工作,该方法并不是依赖于设备本身,而是依赖于apple push机制,所以当苹果push做出改变时, 你获取所谓的唯一标识也就随之失效了。所以此方法还是不可取的。
首次运行
nsuuid:9d820d3a-4429-4918-97f7-a69588b388a4
cfuuid:80f961d0-1e6a-4ecd-a0a9-f58ed858fe20
idfa:687e6a90-50a3-4424-871c-be255d050afd
idfv:8e740a99-283b-4f6a-87ef-443fb7778488
二次运行
nsuuid:23ab8d3d-4f1d-45e2-8bd7-83b451125326
cfuuid:14ddbfcf-67a6-46b7-bb48-4ef2adc5429f
idfa:687e6a90-50a3-4424-871c-be255d050afd
idfv:8e740a99-283b-4f6a-87ef-443fb7778488
卸载后, 重新安装运行
nsuuid:bd934f9c-b7ec-4bd1-b65e-964c66537cab
cfuuid:29654de0-ac93-40f9-98ab-1e10a271af8d
idfa:687e6a90-50a3-4424-871c-be255d050afd
idfv:8e740a99-283b-4f6a-87ef-443fb7778488
重启后运行
nsuuid:82711557-3a17-4b82-8f18-09aadf9dd37b
cfuuid:ffbc73ec-cfbe-414c-870e-77c0714e0347
idfa:687e6a90-50a3-4424-871c-be255d050afd
idfv:8e740a99-283b-4f6a-87ef-443fb7778488
说了这么多, 才发现原来没有一种方法是可行的。没错, 其实自从苹果废除udid后, 就不能达到获取设备真正的唯一标识了。因为这些方法中导致获取的唯一标示产生改变的原因, 或是重新调用方法, 或是重启设备, 或是卸载应用, 或是还原某些标识, 或者刷新系统…
所以, 不能达到从根本上获取唯一标识, 我们只能做到尽可能接近。下面是我用过的方法。
我用的方法是将获取的uuid永久存储在设备的keychain中, 这个方法在应用第一次启动时, 将获取的uuid存储进keychain中, 每次取的时候, 检查本地钥匙串中有没有, 如果没有则需要将获取的uuid存储进去。当你重启设备, 卸载应用再次安装,都不影响, 只是当设备刷机时, keychain会清空, 才会消失, 才会失效。
不只是这一种方法, 你也可以保存除uuid之外,其他合适的标识, 但利用keychain去存储标识的方式应该是最接近的。
开发者可以在应用第一次启动时调用一次,然后将该串存储起来,以便以后替代udid来使用。但是,如果用户删除该应用再次安装时,又会生成新的字符串,所以不能保证唯一识别该设备。这就需要各路高手想出各种解决方案。所以,之前很多应用就采用mac address。但是现在如果用户升级到ios7(及其以后的苹果系统)后,他们机子的mac address就是一样的,没办法做区分,只能弃用此方法,重新使用uuid来标识。如果使用uuid,就要考虑应用被删除后再重新安装时的处理。
一、在应用间利用keychain共享数据
我们可以把keychain理解为一个dictionary,所有数据都以key-value的形式存储,可以对这个dictionary进行add、update、get、delete这四个操作。对于每一个应用来说,keychain都有两个访问区,私有区和公共区。私有区是一个sandbox,本程序存储的任何数据都对其他程序不可见。而要想在将存储的内容放在公共区,需要先声明公共区的名称,官方文档管这个名称叫“keychain access group”,声明的方法是新建一个plist文件,名字随便起,内容如下:
“yourappid.com.yourcompany.whatever”就是你要起的公共区名称,除了whatever字段可以随便定之外,其他的都必须如实填写。这个文件的路径要配置在 project->build setting->code signing entitlements里,否则公共区无效,配置好后,须用你正式的证书签名编译才可通过,否则xcode会弹框告诉你code signing有问题。所以,苹果限制了你只能同公司的产品共享keychain数据,别的公司访问不了你公司产品的keychain。
二、保存私密信息
ios的keychain服务提供了一种安全的保存私密信息(密码,序列号,证书等)的方式,每个ios程序都有一个独立的keychain存储。相对于nsuserdefaults、文件保存等一般方式,keychain保存更为安全,而且keychain里保存的信息不会因app被删除而丢失,所以在重装app后,keychain里的数据还能使用。
首先, 我先推荐两篇文章,里面介绍了如利用keychain和uuid永久获得设备的唯一标识, classes/keychainitemwrapper.m和 。
然后, 再介绍下我使用的方法以及封装的工具类, 在应用里使用使用keychain,我们需要导入security.framework。下面介绍下, 我在其他库基础上封装的一个获取唯一标识的工具类:
#import #import nsstring * const key_udid_instead = @"com.myapp.udid.test"; @interface lzkeychain : nsobject /** 本方法是得到 uuid 后存入系统中的 keychain 的方法 不用添加 plist 文件 程序删除后重装,仍可以得到相同的唯一标示 但是当系统升级或者刷机后,系统中的钥匙串会被清空,此时本方法失效 */ +(nsstring *)getdeviceidinkeychain; @end
#import "lzkeychain.h" @implementation lzkeychain +(nsstring *)getdeviceidinkeychain { nsstring *getudidinkeychain = (nsstring *)[lzkeychain load:key_udid_instead]; nslog(@"从keychain中获取到的 udid_instead %@",getudidinkeychain); if (!getudidinkeychain ||[getudidinkeychain isequaltostring:@""]||[getudidinkeychain iskindofclass:[nsnull class]]) { cfuuidref puuid = cfuuidcreate( nil ); cfstringref uuidstring = cfuuidcreatestring( nil, puuid ); nsstring * result = (nsstring *)cfbridgingrelease(cfstringcreatecopy( null, uuidstring)); cfrelease(puuid); cfrelease(uuidstring); nslog(@"\n \n \n _____重新存储 uuid _____\n \n \n %@",result); [lzkeychain save:key_udid_instead data:result]; getudidinkeychain = (nsstring *)[lzkeychain load:key_udid_instead]; } nslog(@"最终 ———— udid_instead %@",getudidinkeychain); return getudidinkeychain; } #pragma mark - private + (nsmutabledictionary *)getkeychainquery:(nsstring *)service { return [nsmutabledictionary dictionarywithobjectsandkeys: (id)ksecclassgenericpassword,(id)ksecclass, service, (id)ksecattrservice, service, (id)ksecattraccount, (id)ksecattraccessibleafterfirstunlock,(id)ksecattraccessible, nil]; } + (void)save:(nsstring *)service data:(id)data { //get search dictionary nsmutabledictionary *keychainquery = [self getkeychainquery:service]; //delete old item before add new item secitemdelete((cfdictionaryref)keychainquery); //add new object to search dictionary(attention:the data format) [keychainquery setobject:[nskeyedarchiver archiveddatawithrootobject:data] forkey:(id)ksecvaluedata]; //add item to keychain with the search dictionary secitemadd((cfdictionaryref)keychainquery, null); } + (id)load:(nsstring *)service { id ret = nil; nsmutabledictionary *keychainquery = [self getkeychainquery:service]; //configure the search setting //since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute ksecreturndata to kcfbooleantrue [keychainquery setobject:(id)kcfbooleantrue forkey:(id)ksecreturndata]; [keychainquery setobject:(id)ksecmatchlimitone forkey:(id)ksecmatchlimit]; cfdataref keydata = null; if (secitemcopymatching((cfdictionaryref)keychainquery, (cftyperef *)&keydata) == noerr) { @try { ret = [nskeyedunarchiver unarchiveobjectwithdata:(nsdata *)keydata]; } @catch (nsexception *e) { nslog(@"unarchive of %@ failed: %@", service, e); } @finally { } } if (keydata) cfrelease(keydata); return ret; } + (void)delete:(nsstring *)service { nsmutabledictionary *keychainquery = [self getkeychainquery:service]; secitemdelete((cfdictionaryref)keychainquery); } @end
如对本文有疑问, 点击进行留言回复!!
APP调用微信小程序,能拉起小程序,但是onResp回调不会被调用问题
Codeforces Round #657 (Div. 2) B. Dubious Cyrpto(思维,数学)
网友评论