开发中常用工具 - 获取设备的唯一标识、UDID、UUID、keychain保存UUID、判断网络类型等

UDID

  • 全名:Unique Device Identifie(设备唯一标识符)
  • 说明:UDID,即设备唯一标识符,这是除序列号之外每台iOS设备的独一无二的号码。UDID只是和设备相关的,是用来区分每一个唯一的iOS设备(包括iPhone、iPad等),是由40个字符的字母和数字组成的。
  • 作用:可以关联其它各种数据到相关设备上。比如:程序发布前的通过测试版本进行测试等都需要UDID。
  • 获取方法: UDID可以直接通过ITunes查看,手机连接上电脑之后点击序列号就会变成UDID,如下图。也可以通过Xcode查看:点击Window->Devices and Simulators->identifier就能看到,如下图。
通过ITunes查看UDID
通过XCode查看UDID
  • 代码获取方法:
    在iOS5之后,苹果就禁止了通过代码获取UDID。转而用[UIDevice currentDevice].identifierForVendor.UUIDString替代。但是这个不是真正的UDID.关闭的原因是因为隐私问题。之后苹果禁止上架试图获取UDID的应用。

UUID

  • 全名:Universally Unique Identifier(通用唯一标识符)
  • 说明:UUID是一个通过小横线连接起来的32位的十六进制序列。如0DEF9507-EB5A-471A-8BC7-638A0B0A327D。但是UUID并不像UDID一样是惟一的,它只是在某一时空是唯一的,当每次写在应用之后获取到的UUID都是不一样的。比如通过一个for循环打印一下UUID能就能看出不一样:
    for (int i = 0; i < 5; i ++) {
        NSLog(@"uuid %zd = %@", i,[NSUUID UUID].UUIDString);
    }
打印结果

那是不是这样就不能唯一标识了呢?并不是,开发者可以将这个UUID保存在keychain里面,以此作为唯一标识符。接下来会讲到。

  • 代码获取UUID:
NSString * uuid = [NSUUID UUID].UUIDString;

用keychain保存UUID

keychain介绍

苹果在OS X和IOS系统都有提供的一种安全存储敏感信息的工具,即keychain。所谓铭感信息,即用户ID、password、certificate等。keychain里面存储的数据是item。这些item是以key-value的形式存储的,可以理解为Dictonary。利用keychain存储这些信息可以提高用户体验,免除用户重复输入用户名和密码等繁琐的操作。同时,苹果的这套keychain Service安全机制能够保障存储的信息不会被窃取,所以可以用来存储UUID等。

为什么要用keychain?

  1. keychain的数据并非是存放在应用程序的沙盒中,所以即使当用户删除app,存储的资料依然在keychain中。用户再一次安装该应用程序的时候又可以从keychain中获取数据。
  2. keychain的数据有经过加密,更安全。
  3. keychain提供了一个公共区"keychain access group",可以通过这个group实现应用程序之间的数据共享。

keychain中的item

keychain中是存放的item。并且可以存放任意数量的item。keychain会对需要加密的item进行加密保护,比如:密码。而对于像证书就就不会加密。

在苹果提供的API中可以看到有五种类型的item:

  kSecClassInternetPassword //Specifies Internet password items.
  kSecClassGenericPassword  //Specifies generic password items.
  kSecClassCertificate      //Specifies certificate items.
  kSecClassKey              //Specifies key items.
  kSecClassIdentity         //Specifies identity items.

苹果提供了四种操作item的方法,即增、删、改、查操作:

 // 1. 查询已存在的item/items
 SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
 
 // 2. 添加 item/items到keychain
 SecItemAdd(CFDictionaryRef attributes, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
 
 // 3. 更新已存在的item/items
 SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate)
 
 // 4. 删除已存在的 item/items
 SecItemDelete(CFDictionaryRef query)
 

代码环节

可以写一个KeychainWrapper工具类来实现keychain的操作。核心代码如下

// 根据特定的Service创建一个用于操作KeyChain的Dictionary
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service
{
    return [NSMutableDictionary dictionaryWithObjectsAndKeys:
            (__bridge id)(kSecClassGenericPassword), kSecClass,
            service, kSecAttrService,
            service, kSecAttrAccount,
            kSecAttrAccessibleAfterFirstUnlock, kSecAttrAccessible,
            nil];
}

// 保存数据到keychain中
+ (BOOL)saveDate:(id)date withService:(NSString *)service
{
    // 1. 创建dictonary
    NSMutableDictionary * keychainQuery = [self getKeychainQuery:service];
    // 2. 先删除
    SecItemDelete((CFDictionaryRef)keychainQuery);
    // 3. 添加到date到query中
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:date] forKey:(id<NSCopying>)kSecValueData];
    // 4. 存储到到keychain中
    OSStatus status = SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
    
    return status == noErr ? YES : NO;
}

// 从keychain中查找数据
+ (id)searchDateWithService:(NSString *)service
{
    id retsult = nil;
    NSMutableDictionary * keychainQuery = [self getKeychainQuery:service];
    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id<NSCopying>)kSecReturnData];
    [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id<NSCopying>)kSecMatchLimit];
    
    CFTypeRef resultDate = NULL;
    if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, &resultDate)== noErr) {
        @try{
            retsult = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)resultDate];
        }
        @catch(NSException *e){
            NSLog(@"查找数据不存在");
        }
        @finally{
            
        }
    }
    if (resultDate) {
        CFRelease(resultDate);
    }
    return retsult;
}

// 更新keychain中的数据
+ (BOOL)updateDate:(id)date withService:(NSString *)service
{
    NSMutableDictionary * searchDictonary = [self getKeychainQuery:service];
    
    if (!searchDictonary) {return  NO;}
    
    NSMutableDictionary * updateDictonary = [NSMutableDictionary dictionary];
    [updateDictonary setObject:[NSKeyedArchiver archivedDataWithRootObject:date] forKey:(id<NSCopying>)kSecValueData];
    OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictonary, (CFDictionaryRef)updateDictonary);
    return status == noErr ? YES : NO;
}

// 删除keychain中的数据
+ (BOOL)deleteDateiWithService:(NSString *)service
{
    NSMutableDictionary * keychainQuery = [self getKeychainQuery:service];
    OSStatus status = SecItemDelete((CFDictionaryRef)keychainQuery);
    return status == noErr ? YES : NO;
}

使用keychain保存UUID

有了上面的方法,接下来就操作就很简单了:

/**
  先从keychain里面加载uuid 如果没有 就获取uuid并加载到keychain中
 */
+ (NSString *)getUUIDfromKeychain
{
    NSString * uuid = NULL;
    uuid = [KeychainWrapper searchDateWithService:DEMO_UUID];
    if (uuid) {
        return uuid;
    }else{
        uuid = [self getRandomUUID];
        if([KeychainWrapper saveDate:uuid withService:DEMO_UUID]){
            return uuid;
        }else{
            return NULL;
        }
    }
}

+ (NSString *)getRandomUUID
{
    return [NSUUID UUID].UUIDString;
}

打印出来发现获取的uuid是一样的,说明keychain保存成功了:


image

image

IDFA

  • 全名:Identifier for Advertising(广告标示符)
  • 来源:iOS6.0+
  • 说明:IDFA,即广告标示符。这是苹果专门用来给广告商追踪用户而设置的,在同一个设备上的所有APP都会取到相同的值。当然,是否开启IDFA取决于用户的心情,用户可以在:设置 -> 隐私 -> 广告 中关闭广告追踪。


    image

    所以IDFA就存在取不到的情况,所以一般不会只用IDFA识别用户。

  • 代码获取方式:
/**
 *  获取IDFA,如果用户关闭此功能,就会存在娶不到的情况
 */
+ (NSString *)getIDFA
{
    return [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
}

IDFV

  • 全名:Identifier For Vendor
  • 来源:iOS6.0+
  • 说明:IDFV是给供应商(Vender)标识用户用的,也就是说属于同一个供应商的应用的IDFV都是相同的。比如com.vender.app1com.vender.app2这两个BundleID都是属于同一个供应商,那么这两个应用的IDFV都是相同的。原理是通过BundleID的反转的前两部分进行匹配,如果相同就是同一个Vender,共享同一个idfv的值。值得一提的是,IDFV是一定能取到的。但是如果用户将属于同一个Vender的所有App卸载,则IDFV的值会被重置,当再重装此Vender的App时IDFV的值和之前不同。
  • 代码获取方式:
/**
 *  获取IDFV
 */
+ (NSString *)getIDFV
{
   return [[[UIDevice currentDevice] identifierForVendor] UUIDString];
}

获取运营商&&判断网络类型

获取运营商

获取运营商很简单,只需要用到CTTelephonyNetworkInfoCTCarrier两个类即可,值得注意的是,需要导入两个头文件:

#import <CoreTelephony/CTTelephonyNetworkInfo.h> 
#import <CoreTelephony/CTCarrier.h> 

代码:

/**
 *  获取设备运营商
 */
+ (NSString *)getCarrier
{
    CTTelephonyNetworkInfo * info = [[CTTelephonyNetworkInfo alloc]init];
    CTCarrier * carrier = [info subscriberCellularProvider];
    
    NSString * mobile;
    if (!carrier.isoCountryCode) {
        NSLog(@"没有SIM卡");
        mobile = @"无运营商";
    }else{
        mobile = [carrier carrierName];
    }
    
    return mobile;
}

判断网络类型

判断网络类型的方式有几种:

  1. 通过状态栏进行判断:
    缺点:状态栏可以隐藏,一旦隐藏就无法获取。
  2. 用三方框架AFNetworking判断
    缺点:必须导入该框架。
  3. Reachability + CTTelephonyNetworkInfo
    缺点:代码较多
    这里使用第三种方式获取网络状态类型Reachability + CTTelephonyNetworkInfo。Reachability可以到官网去下载Reachability
    Reachability中有三种类型的网络状态:
    NotReachable  // 无网络连接
    ReachableViaWiFi // WIFI
    ReachableViaWWAN // 蜂窝移动类型

所以还需要通过CTTelephonyNetworkInfo对蜂窝移动网络类型判断。CTTelephonyNetworkInfo中蜂窝移动网络类型有:

CTRadioAccessTechnologyGPRS          
CTRadioAccessTechnologyEdge          
CTRadioAccessTechnologyWCDMA         
CTRadioAccessTechnologyHSDPA         
CTRadioAccessTechnologyHSUPA         
CTRadioAccessTechnologyCDMA1x        
CTRadioAccessTechnologyCDMAEVDORev0  
CTRadioAccessTechnologyCDMAEVDORevA  
CTRadioAccessTechnologyCDMAEVDORevB  
CTRadioAccessTechnologyeHRPD         
CTRadioAccessTechnologyLTE  

完整代码:

/**
 *  判断当前网络类型
 */
+ (NSString *)getNetworkType
{
    
    Reachability * reachability = [Reachability reachabilityWithHostName:@"www.baidu.com"];
    NetworkStatus netStatus = [reachability currentReachabilityStatus];
    NSString * networkType = @"";
    
    switch (netStatus) {
        case ReachableViaWiFi:
            networkType = @"WIFI";
            break;
            
        case ReachableViaWWAN:
        {
            // 判断蜂窝移动类型
            CTTelephonyNetworkInfo * networkInfo = [[CTTelephonyNetworkInfo alloc]init];
            if ([networkInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyGPRS]) {
                networkType = @"2G";
            } else if ([networkInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyEdge]) {
                networkType = @"2G";
            } else if ([networkInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyWCDMA]) {
                networkType = @"3G";
            } else if ([networkInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyHSDPA]) {
                networkType = @"3G";
            } else if ([networkInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyHSUPA]) {
                networkType = @"3G";
            } else if ([networkInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMA1x]) {
                networkType = @"3G";
            } else if ([networkInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORev0]) {
                networkType = @"3G";
            } else if ([networkInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORevA]) {
                networkType = @"3G";
            } else if ([networkInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORevB]) {
                networkType = @"3G";
            } else if ([networkInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyeHRPD]) {
                networkType = @"3G";
            } else if ([networkInfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyLTE]) {
                networkType = @"4G";
            }
        }
            break;
            
        case NotReachable:
            networkType = @"当前无网络连接";
            break;
    }
   
    return networkType;
}

结语

我把以上代码都封装到了DeviceInfo中,需要的可以直接拖入这个文件即可使用。github链接:DeviceInfo

参考博客:

  1. keychian
  2. Reachability:
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容