iOS獲取設(shè)備的唯一標(biāo)識的方法總結(jié) 2020年可用方案

iOS獲取設(shè)備的唯一標(biāo)識的方法總結(jié) 2020年可用方案 用不了的我就不寫出來了哈.

一.UUID(Universally Unique Identifier)

UUID是Universally Unique Identifier的縮寫,中文意思是通用唯一識別碼。它是讓分布式系統(tǒng)中的所有元素,都能有唯一的辨識資訊,而不需要透過中央控制端來做辨識資訊的指定。這樣,每個人都可以建立不與其它人沖突的 UUID。在此情況下,就不需考慮數(shù)據(jù)庫建立時的名稱重復(fù)問題。蘋果公司建議使用UUID為應(yīng)用生成唯一標(biāo)識字符串。

獲得的UUID值系統(tǒng)沒有存儲, 而且每次調(diào)用得到UUID,系統(tǒng)都會返回一個新的唯一標(biāo)示符。如果你希望存儲這個標(biāo)示符,那么需要自己將其存儲到NSUserDefaults, Keychain, Pasteboard或其它地方。

CFUUID

從iOS2.0開始,CFUUID就已經(jīng)出現(xiàn)了。它是CoreFoundatio包的一部分,因此API屬于C語言風(fēng)格。CFUUIDCreate 方法用來創(chuàng)建CFUUIDRef,并且可以獲得一個相應(yīng)的NSString,如下代碼:

CFUUIDRef cfuuid = CFUUIDCreate(kCFAllocatorDefault);
NSString *cfuuidString = (NSString*)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault,cfuuid));

獲得的這個CFUUID值系統(tǒng)并沒有存儲。每次調(diào)用CFUUIDCreate,系統(tǒng)都會返回一個新的唯一標(biāo)示符。如果你希望存儲這個標(biāo)示符,那么需要自己將其存儲到NSUserDefaults, Keychain, Pasteboard或其它地方。

NSUUID

NSUUID在iOS 6中才出現(xiàn),這跟CFUUID幾乎完全一樣,只不過它是Objective-C接口。+ (id)UUID 是一個類方法,調(diào)用該方法可以獲得一個UUID。通過下面的代碼可以獲得一個UUID字符串:

NSString *uuid = [[NSUUID UUID] UUIDString];

跟CFUUID一樣,這個值系統(tǒng)也不會存儲,每次調(diào)用的時候都會獲得一個新的唯一標(biāo)示符。如果要存儲的話,你需要自己存儲。在我讀取NSUUID時,注意到獲取到的這個值跟CFUUID完全一樣(不過也可能不一樣)

二.廣告標(biāo)示符(IDFA-identifierForIdentifier)

廣告標(biāo)示符,在同一個設(shè)備上的所有App都會取到相同的值,是蘋果專門給各廣告提供商用來追蹤用戶而設(shè)的。但好在Apple默認(rèn)是允許追蹤的,而且一般用戶都不知道有這么個設(shè)置,所以基本上用來監(jiān)測推廣效果,是戳戳有余了。

它是iOS 6中另外一個新的方法,提供了一個方法advertisingIdentifier,通過調(diào)用該方法會返回一個NSUUID實例,最后可以獲得一個UUID,由系統(tǒng)存儲著的。

#import <AdSupport/AdSupport.h>
NSString *adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];

不過即使這是由系統(tǒng)存儲的,但是有幾種情況下,會重新生成廣告標(biāo)示符。

(1)如果用戶完全重置系統(tǒng)((設(shè)置程序 -> 通用 -> 還原 -> 還原位置與隱私) ,這個廣告標(biāo)示符會重新生成。

(2)另外如果用戶明確的還原廣告(設(shè)置程序-> 通用 -> 關(guān)于本機(jī) -> 廣告 -> 還原廣告標(biāo)示符) ,那么廣告標(biāo)示符也會重新生成。

關(guān)于廣告標(biāo)示符的還原,有一點需要注意:如果程序在后臺運行,此時用戶“還原廣告標(biāo)示符”,然后再回到程序中,此時獲取廣 告標(biāo)示符并不會立即獲得還原后的標(biāo)示符。必須要終止程序,然后再重新啟動程序,才能獲得還原后的廣告標(biāo)示符。

所以IDFA也不可以作為獲取唯一標(biāo)識的方法,來識別用戶。

三.Vendor標(biāo)示符 (IDFV-identifierForVendor)

Vendor標(biāo)示符,是給Vendor標(biāo)識用戶用的,每個設(shè)備在所屬同一個Vender的應(yīng)用里,都有相同的值。其中的Vender是指應(yīng)用提供商,但準(zhǔn)確點說,是通過BundleID的反轉(zhuǎn)的前兩部分進(jìn)行匹配,如果相同就是同一個Vender,例如對于com.taobao.app1, com.taobao.app2 這兩個BundleID來說,就屬于同一個Vender,共享同一個IDFV的值。和IDFA不同的是,IDFV的值是一定能取到的,所以非常適合于作為內(nèi)部用戶行為分析的主id,來標(biāo)識用戶,替代OpenUDID。

它是iOS 6中新增的,跟advertisingIdentifier一樣,該方法返回的是一個 NSUUID對象,可以獲得一個UUID。如果滿足條件“相同的一個程序里面-相同的vendor-相同的設(shè)備”,那么獲取到的這個屬性值就不會變。如果是“相同的程序-相同的設(shè)備-不同的vendor,或者是相同的程序-不同的設(shè)備-無論是否相同的vendor”這樣的情況,那么這個值是不會相同的。

NSString *strIDFV = [[[UIDevice currentDevice]identifierForVendor]UUIDString];

但是如果用戶將屬于此Vender的所有App卸載,則IDFV的值會被重置,即再重裝此Vender的App,IDFV的值和之前不同。

四.推送token+bundle_id

推送token+bundle_id的方法:

1、應(yīng)用中增加推送用來獲取token

2、獲取應(yīng)用bundle_id

3、根據(jù)token+bundle_id進(jìn)行散列運算

apple push token保證設(shè)備唯一,但必須有網(wǎng)絡(luò)情況下才能工作,該方法并不是依賴于設(shè)備本身,而是依賴于apple push機(jī)制,所以當(dāng)蘋果push做出改變時, 你獲取所謂的唯一標(biāo)識也就隨之失效了。所以此方法還是不可取的。

如何正確的獲取設(shè)備的唯一標(biāo)識

我用的方法是將獲取的UUID永久存儲在設(shè)備的KeyChain中, 這個方法在應(yīng)用第一次啟動時, 將獲取的UUID存儲進(jìn)KeyChain中, 每次取的時候, 檢查本地鑰匙串中有沒有, 如果沒有則需要將獲取的UUID存儲進(jìn)去。當(dāng)你重啟設(shè)備, 卸載應(yīng)用再次安裝,都不影響, 只是當(dāng)設(shè)備刷機(jī)時, KeyChain會清空, 才會消失, 才會失效。

不只是這一種方法, 你也可以保存除UUID之外,其他合適的標(biāo)識, 但利用KeyChain去存儲標(biāo)識的方式應(yīng)該是最接近的。

利用keyChain和UUID永久獲得設(shè)備的唯一標(biāo)識

開發(fā)者可以在應(yīng)用第一次啟動時調(diào)用一次,然后將該串存儲起來,以便以后替代UDID來使用。但是,如果用戶刪除該應(yīng)用再次安裝時,又會生成新的字符串,所以不能保證唯一識別該設(shè)備。這就需要各路高手想出各種解決方案。所以,之前很多應(yīng)用就采用MAC Address。但是現(xiàn)在如果用戶升級到iOS7(及其以后的蘋果系統(tǒng))后,他們機(jī)子的MAC Address就是一樣的,沒辦法做區(qū)分,只能棄用此方法,重新使用UUID來標(biāo)識。如果使用UUID,就要考慮應(yīng)用被刪除后再重新安裝時的處理。

什么是鑰匙串?

在應(yīng)用間利用KeyChain共享數(shù)據(jù)

我們可以把KeyChain理解為一個Dictionary,所有數(shù)據(jù)都以key-value的形式存儲,可以對這個Dictionary進(jìn)行add、update、get、delete這四個操作。對于每一個應(yīng)用來說,KeyChain都有兩個訪問區(qū),私有區(qū)和公共區(qū)。私有區(qū)是一個sandbox,本程序存儲的任何數(shù)據(jù)都對其他程序不可見。而要想在將存儲的內(nèi)容放在公共區(qū),需要先聲明公共區(qū)的名稱,官方文檔管這個名稱叫“keychain access group”,聲明的方法是新建一個plist文件,名字隨便起,內(nèi)容如下

plist.png

"yourAppID.com.yourCompany.whatever"就是你要起的公共區(qū)名稱,除了whatever字段可以隨便定之外,其他的都必須如實填寫。這個文件的路徑要配置在 Project->build setting->Code Signing Entitlements里,否則公共區(qū)無效,配置好后,須用你正式的證書簽名編譯才可通過,否則xcode會彈框告訴你code signing有問題。所以,蘋果限制了你只能同公司的產(chǎn)品共享KeyChain數(shù)據(jù),別的公司訪問不了你公司產(chǎn)品的KeyChain。

保存私密信息

iOS的Keychain服務(wù)提供了一種安全的保存私密信息(密碼,序列號,證書等)的方式,每個ios程序都有一個獨立的keychain存儲。相對于NSUserDefaults、文件保存等一般方式,keychain保存更為安全,而且keychain里保存的信息不會因App被刪除而丟失,所以在重裝App后,keychain里的數(shù)據(jù)還能使用。

項目配置

在項目的 Build Phases中添加Security.framework

在項目的 Signing & Capabilities中添加Keychain Sharing,加入Keychain Groups分組。每個app都設(shè)置相同的Keychain Groups

image

實現(xiàn)代碼

介紹下我使用的方法以及封裝的工具類, 在應(yīng)用里使用使用keyChain,我們需要導(dǎo)入Security.framework。下面介紹下, 我在其他庫基礎(chǔ)上封裝的一個獲取唯一標(biāo)識的工具類:

#import <Foundation/Foundation.h>
#import <Security/Security.h>
@interface BGKeychainTool : NSObject

/**
本方法是得到 UUID 后存入系統(tǒng)中的 keychain 的方法
不用添加 plist 文件
程序刪除后重裝,仍可以得到相同的唯一標(biāo)示
但是當(dāng)系統(tǒng)升級或者刷機(jī)后,系統(tǒng)中的鑰匙串會被清空,此時本方法失效
*/
+(NSString*)getDeviceIDInKeychain;

@end

.m 文件實現(xiàn)

#import "BGKeychainTool.h"

@implementation BGKeychainTool

+(NSString *)getDeviceIDInKeychain {
    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];//獲取app版本信息
    NSString *key = [NSString stringWithFormat:@"%@.uniqueid",[infoDictionary objectForKey:@"CFBundleIdentifier"]];
    NSString *getUDIDInKeychain = (NSString *)[BGKeychainTool load:key];
    NSLog(@"從keychain中獲取到的 UDID_INSTEAD %@",getUDIDInKeychain);
    if (!getUDIDInKeychain || [getUDIDInKeychain isEqualToString:@""] || [getUDIDInKeychain isKindOfClass:[NSNull class]]) {
        getUDIDInKeychain = [UIDevice currentDevice].identifierForVendor.UUIDString;
        if(getUDIDInKeychain.length == 0 || [getUDIDInKeychain isEqualToString:@"00000000-0000-0000-0000-000000000000"])  {
            CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
            CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuidRef);
            getUDIDInKeychain = (NSString *)CFBridgingRelease(CFStringCreateCopy(NULL, uuidString));
            CFRelease(uuidRef);
            CFRelease(uuidString);
            NSLog(@"\n \n \n _____重新存儲 UUID _____\n \n \n  %@",getUDIDInKeychain);
        }
        [BGKeychainTool save:key data:getUDIDInKeychain];
    }
    NSLog(@"最終 ———— UDID_INSTEAD %@",getUDIDInKeychain);
    return getUDIDInKeychain;
}

下面有幾個私有方法

通過 key 獲取數(shù)據(jù)

+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
    return [NSMutableDictionary dictionaryWithObjectsAndKeys:
            (id)kSecClassGenericPassword,(id)kSecClass,
            service, (id)kSecAttrService,
            service, (id)kSecAttrAccount,
            (id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
            nil];
}

保存數(shù)據(jù)至鑰匙串中

+(void)save:(NSString*)service data:(id)data{
    //Get search dictionary
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    //Delete old item before add new item
    SecItemDelete((CFDictionaryRef)keychainQuery);
    //Add new object to search dictionary(Attention:the data format)
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data]forKey:(id)kSecValueData];
    //Add item to keychain with the search dictionary
    SecItemAdd((CFDictionaryRef)keychainQuery,NULL);
}

從鑰匙串中加載數(shù)據(jù)

+ (id)load:(NSString *)service {
    id ret = nil;
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    //Configure the search setting
    //Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue
    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
    [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
    CFDataRef keyData = NULL;
    if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
        @try {
            ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
        } @catch (NSException *e) {
            NSLog(@"Unarchive of %@ failed: %@", service, e);
        } @finally {
        }
    }
    if (keyData)
        CFRelease(keyData);
    return ret;
}

刪除數(shù)據(jù)

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

首次編譯

編譯結(jié)果

再次編譯

再次編譯
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容