iOS上一些设备信息的获取

2015-10-01 | 阅读

设备识别码UUID的获取

iOS为了所谓的安全性,导致了无法拿到其他IMEI或MAC地址等其他可以用来进行设备识别的东西,而获取并保存设备识别码,一般通过放在KeyChain的UUID来实现.

简单来说,通过任何一个较好地随机算法来进行HASH,可以得到一段基本上在地球上唯一的编码,在第一次使用应用时,将该编码放在KeyChain中,而Keychain的特点是,即使应用删除,其保存的内容还在,所以下次再重装应用,依然能使用之前的编码来识别自己.而这个编码也就是UUID.keychain还有一个特点,就是一个公司的不同应用可以共享keychain.

而苹果提供的UUID,是在应用安装时,对特定设备和特定应用来进行标示唯一,在应用安装后每次去获取UUID都会是相同的结果.但是删除应用重新安装后,又将获得一个全新的UUID,所以保存到Keychain中就可以完全胜任设备识别码的功能.

UUID可以调用[[UIDevice currentDevice] identifierForVendor].UUIDString 方法来获取.获取了UUID后就要考虑将其放入KeyChain中.

Keychain读取的简单封装

Keychain是iOS中相对较为安全的保存数据的地方,一般在这里保存UUID,也就是设备识别码.Keychain里类似UserDefault,也是字典结构的.使用keychain要引入系统的Security库.下面是简单的封装代码:

/**
 *  查找KeyChain的查询请求字典
 *
 *  @param service 目录
 *
 *  @return 查询请求字典
 */
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service ;

/**
 *  在service目录下存储内容,如果已有内容,会获取并删除,然后更新新的内容.
 *
 *  @param service 目录
 *  @param data    数据,为NSDictionary
 */
+ (void)save:(NSString *)service data:(id)data ;

/**
 *  加载目录下的内容,也是返回一个字典,获取查询请求,然后使用
 *
 *  @param service 目录
 *
 *  @return 字典
 */
+ (id)load:(NSString *)service ;

/**
 *  删除该位置的内容
 *
 *  @param service 位置
 */
+ (void)delete:(NSString *)service ;

+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
    return [NSMutableDictionary dictionaryWithObjectsAndKeys:
            (__bridge_transfer id)kSecClassGenericPassword,(__bridge_transfer id)kSecClass,
            service, (__bridge_transfer id)kSecAttrService,
            service, (__bridge_transfer id)kSecAttrAccount,
            (__bridge_transfer id)kSecAttrAccessibleAfterFirstUnlock,(__bridge_transfer 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((__bridge_retained CFDictionaryRef)keychainQuery);
    //Add new object to search dictionary(Attention:the data format)
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge_transfer id)kSecValueData];
    //Add item to keychain with the search dictionary
    SecItemAdd((__bridge_retained CFDictionaryRef)keychainQuery, NULL);
}

+ (id)load:(NSString *)service {
    id ret = nil;
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    //Configure the search setting
    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnData];
    [keychainQuery setObject:(__bridge_transfer id)kSecMatchLimitOne forKey:(__bridge_transfer id)kSecMatchLimit];
    CFDataRef keyData = NULL;
    if (SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
        @try {
            ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge_transfer NSData *)keyData];
        } @catch (NSException *e) {
            NSLog(@"Unarchive of %@ failed: %@", service, e);
        } @finally {
        }
    }
    return ret;
}

+ (void)delete:(NSString *)service {
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery);
}

使用时,就很简单,首先,要赋予字典一个键值,然后存储,读也是根据键值进行读取,与UseDefault类似:

NSDictionary* imutableDic = [BeaconKeychainWrapper load:Beacon_Imutable_Phone_Data];
[BeaconKeychainWrapper save:Beacon_Imutable_Phone_Data data:ret];

获取当前设备型号

只获取iphone型号的代码如下:

+(NSString*)currentDeviceString
{
    struct utsname systemInfo;
    uname(&systemInfo);
    NSString *deviceString = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
    
    if ([deviceString isEqualToString:@"iPhone1,1"])
    {
        return @"iPhone";
    }
    if ([deviceString isEqualToString:@"iPhone1,2"])
    {
        return @"iPhone3G";
    }
    if ([deviceString isEqualToString:@"iPhone2,1"])
    {
        return @"iPhone3GS";
    }
    if ([deviceString isEqualToString:@"iPhone3,1"])
    {
        return @"iPhone4";
    }
    if ([deviceString isEqualToString:@"iPhone4,1"]){
        return @"iPhone 4S";
    }
    if ([deviceString isEqualToString:@"iPhone6,1"]){
        return @"iPhone5";
    }
    if ([deviceString isEqualToString:@"iPhone6,2"]){
        return @"iPhone5s";
    }
    if ([deviceString isEqualToString:@"iPhone7,1"]){
        return @"iPhone6plus";
    }
    if ([deviceString isEqualToString:@"iPhone7,2"]){
        return @"iPhone6";
    }
    if ([deviceString isEqualToString:@"iPhone8,1"]){
        return @"iPhone6s";
    }
    if ([deviceString isEqualToString:@"iPhone8,2"]){
        return @"iPhone6s plus";
    }
    if ([deviceString isEqualToString:@"i386"])         return @"Simulator";
    if ([deviceString isEqualToString:@"x86_64"])       return @"Simulator";
    return deviceString;
}

UIDevice中可以获取的消息:

先使用UIDevice currentDevice来获取当前设备.而在UIDevice中可以获取的数据有如下:

  • name : 设备名称,”xxx’s iPhone”
  • model : 设备型号,”iPhone”,”iPad”,这种类型
  • localizedModel : model下的详细型号,与model组合,就可以得到上小节中的deviceString.
  • systemName : 只有”iOS”
  • systemVersion : 操作系统版本号
  • identifierForVendor : UUID,但是是NSUUID类型的.

Bundle中可以获得的信息

  1. 应用名称:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]
  2. 应用版本号: [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]
  3. 应用build版本号: [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]

获取运营商消息

基站消息获取不到,只能获取简单的运营商消息.

CTTelephonyNetworkInfo *networkInfo = [[CTTelephonyNetworkInfo alloc] init];
NSString *operators;
if (networkInfo && networkInfo.subscriberCellularProvider) {
    operators = networkInfo.subscriberCellularProvider.carrierName;
}

需要引入库CoreTelephony.

获取IP地址:

#include <ifaddrs.h>
#include <arpa/inet.h>
#include <net/if.h>

#define IOS_CELLULAR    @"pdp_ip0"
#define IOS_WIFI        @"en0"
#define IOS_VPN         @"utun0"
#define IP_ADDR_IPv4    @"ipv4"
#define IP_ADDR_IPv6    @"ipv6"

- (NSString *)getIPAddress:(BOOL)preferIPv4
{
    NSArray *searchArray = preferIPv4 ?
                            @[ IOS_VPN @"/" IP_ADDR_IPv4, IOS_VPN @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] :
                            @[ IOS_VPN @"/" IP_ADDR_IPv6, IOS_VPN @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4 ] ;

    NSDictionary *addresses = [self getIPAddresses];
    NSLog(@"addresses: %@", addresses);

    __block NSString *address;
    [searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop)
        {
            address = addresses[key];
            if(address) *stop = YES;
        } ];
    return address ? address : @"0.0.0.0";
}

- (NSDictionary *)getIPAddresses
{
    NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];

    // retrieve the current interfaces - returns 0 on success
    struct ifaddrs *interfaces;
    if(!getifaddrs(&interfaces)) {
        // Loop through linked list of interfaces
        struct ifaddrs *interface;
        for(interface=interfaces; interface; interface=interface->ifa_next) {
            if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) {
                continue; // deeply nested code harder to read
            }
            const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
            char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
            if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {
                NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
                NSString *type;
                if(addr->sin_family == AF_INET) {
                    if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
                        type = IP_ADDR_IPv4;
                    }
                } else {
                    const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;
                    if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
                        type = IP_ADDR_IPv6;
                    }
                }
                if(type) {
                    NSString *key = [NSString stringWithFormat:@"%@/%@", name, type];
                    addresses[key] = [NSString stringWithUTF8String:addrBuf];
                }
            }
        }
        // Free memory
        freeifaddrs(interfaces);
    }
    return [addresses count] ? addresses : nil;
}

上述代码是从stackOverflow找到的,在getIPAddresses函数中获取所有可用的IP地址,it can return a dictionary of all addresses found, skipping addresses for not up interfaces, or addresses associated with loopback.. 然后在getIPAddress:函数中,根据需要来从列表中获取自己所需要的IP地址.即,当需要IPv4的地址时,先搜索IPv4的地址,当需要IPv6的时候,先搜索IPv6的地址. 而地址还有一个优先级,VPN - WIFI - CELLULAR.

获取当前大致电量

[UIDevice currentDevice].batteryMonitoringEnabled = YES;
float deviceLevel = [UIDevice currentDevice].batteryLevel;

上述方法只能获取大致电量,精确度在5%之内.值在 -1 到1 之内,对于模拟器,其值一直会是-1.

获取移动网络类型

+ (NSString*)getDetailNGString{
    CTTelephonyNetworkInfo *networkInfo = [[CTTelephonyNetworkInfo alloc] init];
    NSString* detailAccessTechnology= @"NONE";
    if (networkInfo.currentRadioAccessTechnology) {
        detailAccessTechnology = networkInfo.currentRadioAccessTechnology ;
        if ([detailAccessTechnology isEqualToString:CTRadioAccessTechnologyGPRS]) {
            return @"MOBILE-2G";
        } else if ([detailAccessTechnology isEqualToString:CTRadioAccessTechnologyEdge]) {
            return @"MOBILE-2G";
        }else if ([detailAccessTechnology isEqualToString:CTRadioAccessTechnologyWCDMA]) {
            return @"MOBILE-3G";
        }else if ([detailAccessTechnology isEqualToString:CTRadioAccessTechnologyHSDPA]) {
            return @"MOBILE-3G";
        }else if ([detailAccessTechnology isEqualToString:CTRadioAccessTechnologyHSUPA]) {
            return @"MOBILE-3G";
        }else if ([detailAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMA1x]) {
            return @"MOBILE-3G";
        }else if ([detailAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORev0]) {
            return @"MOBILE-3G";
        }else if ([detailAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORevA]) {
            return @"MOBILE-3G";
        }else if ([detailAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORevB]) {
            return @"MOBILE-3G";
        }else if ([detailAccessTechnology isEqualToString:CTRadioAccessTechnologyeHRPD]) {
            return @"MOBILE-3G";
        }else if ([detailAccessTechnology isEqualToString:CTRadioAccessTechnologyLTE]) {
            return @"MOBILE-4G";
        }
    }
    return nil;
}