iOS应用开发中的设备标识

对于iOS应用开发者来说,苹果所提供的官方后台系统实际上就是iTunes Connect了。通过iTunes Connect我们创建应用记录,提交应用给苹果审核,发布应用,通过iTunes Connect我们可以配置银行卡收钱( 这个很重要:) ),我们可以看到应用的下载量和收据数据报表。

但总体来说iTunes Connect提供的功能还比较有限,而且基本不能定制(除非你能说服苹果)。

对于应用发布后的跟踪和数据收集,很多时候是iTunes Connect之外的事情,甚至有些开发者对于闪退日志收集等也抛弃了iTunes Connect的crash report。那么一个识别具体设备的标志,或者说能够区分不同设备的方法就显得很重要。这篇文章我简要整理一下。

大家可以明确一点,为了保护用户隐私,苹果并没开放太多API给开发者,使得设备的数据追踪变得越来越难。

IMEI、IMSI、ICCID这类在iTunes Mac客户端可以直接看到的东西,现在都不要想着能通过API在程序中获取到。

0. UDID

在iOS6.1及之前,我们可以再UIKit.framework的UIDevice类中看到一个属性,那就是uniqueIdentifier,也就是我们通常所提到的UDID。

但这个属性的声明后面,有NS_DEPRECATED_IOS(2_0, 5_0),意思就是5.0开始就是deprecated的了,是过时的,不建议再继续使用。

到了iOS SDK的7.0版本,在UIDevice类中,就再也找不到这个uniqueIdentifier属性了。而且苹果方面明确表示在2013年5月份之后不再对此支持。即使使用老版本的SDK,也不一定能通过苹果审核,听说有人还尝到过Rejected的苦头。

1. identifierForVendor (IDFV)

貌似也有人简略为IDFV,这是苹果安抚大家的一个UDID的替代品,也是UIDevice类的属性。

按照苹果的文档说明,这个IDFV在同一设备上的所有同Vendor应用得到的ID是相同的,而不同的设备就有不同的IDFV。当这个设备上,同Vendor的所有应用都被卸载掉之后,不能保证同一设备再次安装这个Vendor的应用时,得到同样的ID。

简单来说,如果一个设备上只装了你一个应用,卸载掉再装ID也许就不同了。这样,对于唯一设备的定义就和原来UDID的很不同。这点并不令广大开发者感到满意。

2. MAC地址

如上所述,identifierForVendor不是很令大家满意,于是各种民间方法就出现了。一个方案就是用MAC地址。

学过计算机网络课程的同学们应该了解,要先完成底层网络通信实现MAC地址是必须有的,而这个在网卡制造时要保证全球唯一的,一个设备通常一个网卡就够用了,所以这个在一定程度上可以作为设备标识。

于是乎,拿出了各种底层库,做各种计算,拿到一个MAC地址字符串。

下面这段是网络上比较流行的:

- (NSString *) macAddress
{

    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 *outstring = [NSString stringWithFormat:@"%02X:%02X:%02X:%02X:%02X:%02X",
                           *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];
    free(buf);

    return outstring;
}

而这段需要引入几个系统库:

#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_dl.h>

嗯,不错,这样貌似就可以获取比较有意义的设备标识了。如果这个时候,你沾沾自喜了,那你还是不了解苹果为了保护用户隐私,下了多大力气。

很遗憾第告诉大家,在装有iOS7的设备上,这段代码计算出的值,永远是一个恒定的:

02:00:00:00:00:00

所以,梦又碎了。。

3. advertisingIdentifier (IDFA)

苹果为了完善自己的生态圈,在2010年前后推出了iAd广告网络。那么这个advertisingIdentifier和这个iAd的关系就不言自喻了。如果不了解广告也没关系,简单来讲,现在的互联网广告精准投放需要了解用户数据,基于这些信息是的广告更有效率,唯一标识就很重要,就用到了IDFA。

advertisingIdentifier在AdSupport.framework的ASIdentifierManager类中,是其中两个属性中的一个。

可以说,用这个IDFA标识设备应该还是很精准的(不然iAd就彻底不用玩了),很多开发者还在使用。

但再次,非常遗憾地说,貌似最近苹果对这个的要求也越来越严格了。

我这里收到过的一次拒审原因是:

PLA 3.3.12

In addition, we found your app uses the iOS Advertising Identifier for purposes other than to serve ads. This does not comply with the terms of the iOS Developer Program License Agreement, as required by the App Store Review Guidelines.

Specifically, section 3.3.12 of the iOS Developer Program License Agreement states:

"You and Your Applications (and any third party with whom you have contracted to serve advertising) may use the Advertising Identifier, and any information obtained through the use of the Advertising Identifier, only for the purpose of serving advertising. If a user resets the Advertising Identifier, then You agree not to combine, correlate, link or otherwise associate, either directly or indirectly, the prior Advertising Identifier and any derived information with the reset Advertising Identifier."

Please check your code - including any third-party libraries - to remove inappropriate uses of:

class: ASIdentifierManager
selector: advertisingIdentifier
framework: AdSupport.framework

所以,还在“滥用”IDFA标识设备的朋友们要小心了。

4. 接下来怎么办?

“这是个很好的问题”一般演讲者觉得提问者的问题很难回答的时候会这么说,然后接下来绕弯子兜圈子。。。当然这里我不会。

原则上来说,iOS的开发者是生长在苹果的生态圈,需要与苹果合作,尊重其理念。所以这里的结论是,最好的办法,就是不去标识设备ID。

但人是活的,所以这里有一个比较俗套的办法:

前面讲到IDFV不能令人满意,很大程度上是因为卸载之后重装ID就变了。我们在卸载一个应用的时候,其沙箱内的数据应该会一起不见,那有没有沙箱之外的地方呢?答案自然是肯定的,不过按照苹果的说法,沙箱之外需要系统的API协助。一个比较直观的选择就是Key Chain。所以通俗的方案就是自己生成可见范围内的唯一ID,存入Key Chain,下次再来重装依然可以读到。

在苹果对各种ID封杀后,很多民间的做法就是这种思路。这个做法对于一般条件下的数据收集应该足够了,但如果考虑到设备黑名单等安全识别,还差得远。

后面有机会我们再来讨论这个问题,设备标识的事儿就整理到这里。欢迎大家给出更好的方案来唯一标识设备,欢迎跟帖。

附一张转自Cocoachina的图片,不考虑现今实际的可用情况,对比一下这些ID们的差异:

各类Device ID相关标识的比较

各类Device ID相关标识的比较

相关文章:

此条目发表在 iOS, iOS开发基础 分类目录,贴了 , , , , , 标签。将固定链接加入收藏夹。

iOS应用开发中的设备标识》有 3 条评论

  1. 小岛一方 说:

    加广告机可以用idfa了

  2. 不错,值得收藏分享!

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>