UDID
- 全名:Unique Device Identifie(裝置唯一識別符號)
- 說明:UDID,即裝置唯一識別符號,這是除序列號之外每臺iOS裝置的獨一無二的號碼。UDID只是和裝置相關的,是用來區分每一個唯一的iOS裝置(包括iPhone、iPad等),是由40個字元的字母和數字組成的。
- 作用:可以關聯其它各種資料到相關裝置上。比如:程式釋出前的通過測試版本進行測試等都需要UDID。
- 獲取方法: UDID可以直接通過ITunes檢視,手機連線上電腦之後點選序列號就會變成UDID,如下圖。也可以通過Xcode檢視:點選Window->Devices and Simulators->identifier就能看到,如下圖。
- 程式碼獲取方法:
在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?
- keychain的資料並非是存放在應用程式的沙盒中,所以即使當使用者刪除app,儲存的資料依然在keychain中。使用者再一次安裝該應用程式的時候又可以從keychain中獲取資料。
- keychain的資料有經過加密,更安全。
- 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儲存成功了:
IDFA
- 全名:Identifier for Advertising(廣告標示符)
- 來源:iOS6.0+
- 說明:IDFA,即廣告標示符。這是蘋果專門用來給廣告商追蹤使用者而設定的,在同一個裝置上的所有APP都會取到相同的值。當然,是否開啟IDFA取決於使用者的心情,使用者可以在:設定 -> 隱私 -> 廣告 中關閉廣告追蹤。 所以IDFA就存在取不到的情況,所以一般不會只用IDFA識別使用者。
- 程式碼獲取方式:
/**
* 獲取IDFA,如果使用者關閉此功能,就會存在娶不到的情況
*/
+ (NSString *)getIDFA
{
return [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
}
複製程式碼
IDFV
- 全名:Identifier For Vendor
- 來源:iOS6.0+
- 說明:IDFV是給供應商(Vender)標識使用者用的,也就是說屬於同一個供應商的應用的IDFV都是相同的。比如
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 + 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
參考部落格: