[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
參考博客: