1. 程式人生 > >iOS 獲取裝置唯一識別碼 IDFV+keychain

iOS 獲取裝置唯一識別碼 IDFV+keychain

最近專案中需要獲取到裝置的唯一標誌傳送給後臺儲存備用,在UDID UUID IDFA等都存在諸多問題(什麼問題可以自己查閱資料)的情況下,選擇了 IDFV+keychain(當然特殊情況下也存在些許問題,但基本無影響,非常夠用)。

identifierForVendor是apple給供應商唯一的一個值,也就是說同一個公司發行的的app在相同的裝置上執行的時候會有這個相同的識別符號。然而,如果使用者刪除了這個供應商的所有app然後再重新安裝的話,這個識別符號就會不一致。所以要結合keychain使用,在第一次使用App的時候把 IDFV存到keychain中,以後即使APP寫在重灌,我們只需要從keychain中取跟我們APP相關聯的IDFV值就可以了,這樣既保證了唯一性有保證了永續性。

廢話不多說,下面直接來說怎麼實現!

 IDFV的獲取方法:

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

我們現在把IDFV+keychain封裝成一個工具類:

新建一個繼承自NSObject的類 IDFVTools 用來封裝我們的IDFV+keychain 

類中匯入  #import <UIKit/UIKit.h> 不然呼叫 UIDevice 會報錯

在IDFVTools.h中

+ (void)save:(NSString *)service data:(id)data;
 
+ (id)load:(NSString *)service;
 
+ (void)delete:(NSString *)service;
 
+ (NSString *)getIDFV;

在IDFVTools.m中

+ (NSString *)getIDFV
 
{
    //定義存入keychain中的賬號 一個標識 表示是某個app儲存的內容 bundle id最好
    NSString * const KEY_USERNAME = @"com.wdw.zzzz.username";
    NSString * const KEY_PASSWORD = @"com.wdw.zzzz.password";
    
    //測試用 清除keychain中的內容
    //[IDFVTools delete:KEY_USERNAME_PASSWORD];
    
    //讀取賬號中儲存的內容
    NSMutableDictionary *readUserDataDic = (NSMutableDictionary *)[IDFVTools load:KEY_USERNAME];
    //NSLog(@"keychain==%@",readUserDataDic);
    
    
    if (!readUserDataDic)
    {//如果是第一次 肯定獲取不到 這個時候就儲存一個
        
        NSString *deviceIdStr = [[[UIDevice currentDevice] identifierForVendor] UUIDString];//獲取IDFV
        //NSLog(@"identifierStr==%@",identifierStr);
        
        NSMutableDictionary *needSaveDataDic = [NSMutableDictionary dictionaryWithObject:deviceIdStr forKey:KEY_PASSWORD];
        //進行儲存 並返回這個資料
        [IDFVTools save:KEY_USERNAME data:needSaveDataDic];
        
        return deviceIdStr;
    }
    else{return [readUserDataDic objectForKey:KEY_PASSWORD];}
}
 
//儲存
+ (void)save:(NSString *)service data:(id)data
{
    //Get search dictionary
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    
    //Delete old item before add new item
    SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
    
    //Add new object to search dictionary(Attention:the data format)
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];
    
    //Add item to keychain with the search dictionary
    SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL);
}
 
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service
{
    return [NSMutableDictionary dictionaryWithObjectsAndKeys: (__bridge id)kSecClassGenericPassword,(__bridge id)kSecClass, service, (__bridge id)kSecAttrService, service, (__bridge id)kSecAttrAccount, (__bridge id)kSecAttrAccessibleAfterFirstUnlock,(__bridge id)kSecAttrAccessible, nil];
}
 
//取出
+ (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:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
    
    [keychainQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
    
    CFDataRef keyData = NULL;
    
    if (SecItemCopyMatching((__bridge 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;
}
 
//刪除
+ (void)delete:(NSString *)service
{
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
}

哪裡需要呼叫只需要

[IDFVTools getIDFV];

彩蛋!

除了上述方式還有一個操作鑰匙串的好用的第三方,就是SAMKeychain!

如果你的專案用了cocopods  那麼可以直接下載 具體可以看這裡:

如果你的專案第三方框架都是手動匯入 不喜歡用cocopods 可以直接參考gitHub:

呼叫的時候我們可以封裝成一個方法:

+ (NSString *)getUniqueDevice
{
    NSString *appName=[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey];
    NSString *strApplicationUUID =  [SAMKeychain passwordForService:appName account:account];
    if (strApplicationUUID == nil)
    {
        strApplicationUUID  = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
        NSError *error = nil;
        SAMKeychainQuery *query = [[SAMKeychainQuery alloc] init];
        query.service = appName;
        query.account = account;
        query.password = strApplicationUUID;
        query.synchronizationMode = SAMKeychainQuerySynchronizationModeNo;
        [query save:&error];
 
    }
    return strApplicationUUID;
}