[UIDevice currentDevice].identifierForVendor.UUIDString
替代。可是这个不是真正的UDID.关闭的缘由是由于隐私问题。以后苹果禁止上架试图获取UDID的应用。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里面,以此做为惟一标识符。接下来会讲到。html
NSString * uuid = [NSUUID UUID].UUIDString;
苹果在OS X和IOS系统都有提供的一种安全存储敏感信息的工具,即keychain。所谓铭感信息,即用户ID、password、certificate等。keychain里面存储的数据是item。这些item是以key-value的形式存储的,能够理解为Dictonary。利用keychain存储这些信息能够提升用户体验,免除用户重复输入用户名和密码等繁琐的操做。同时,苹果的这套keychain Service安全机制可以保障存储的信息不会被窃取,因此能够用来存储UUID等。git
keychain中是存放的item。而且能够存听任意数量的item。keychain会对须要加密的item进行加密保护,好比:密码。而对于像证书就就不会加密。github
在苹果提供的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的操做。核心代码以下app
// 根据特定的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 若是没有 就获取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保存成功了:dom
因此IDFA就存在取不到的状况,因此通常不会只用IDFA识别用户。ide
/** * 获取IDFA,若是用户关闭此功能,就会存在娶不到的状况 */ + (NSString *)getIDFA { return [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]; }
com.vender.app1
和com.vender.app2
这两个BundleID都是属于同一个供应商,那么这两个应用的IDFV都是相同的。原理是经过BundleID的反转的前两部分进行匹配,若是相同就是同一个Vender,共享同一个idfv的值。值得一提的是,IDFV是必定能取到的。可是若是用户将属于同一个Vender的全部App卸载,则IDFV的值会被重置,当再重装此Vender的App时IDFV的值和以前不一样。 /** * 获取IDFV */ + (NSString *)getIDFV { return [[[UIDevice currentDevice] identifierForVendor] UUIDString]; }
获取运营商很简单,只须要用到CTTelephonyNetworkInfo
和CTCarrier
两个类便可,值得注意的是,须要导入两个头文件:工具
#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; }
判断网络类型的方式有几种:
AFNetworking
判断这里使用第三种方式获取网络状态类型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
参考博客: