美文网首页将来跳槽用A知识点2上海快风信息科技有限公司
开发中常用工具 - 获取设备的唯一标识、UDID、UUID、ke

开发中常用工具 - 获取设备的唯一标识、UDID、UUID、ke

作者: NotFunGuy | 来源:发表于2018-03-11 20:12 被阅读656次

    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:

    相关文章

      网友评论

        本文标题:开发中常用工具 - 获取设备的唯一标识、UDID、UUID、ke

        本文链接:https://www.haomeiwen.com/subject/sigwfftx.html