(絕對有用)iOS獲取UUID,並使用keychain存儲

UDID被棄用,使用UUID來作爲設備的唯一標識。獲取到UUID後,如果用NSUserDefaults存儲,當程序被卸載後重裝時,再獲得的UUID和之前就不同了。使用keychain存儲可以保證程序卸載重裝時,UUID不變。但當刷機或者升級系統後,UUID還是會改變的。但這仍是目前爲止最佳的解決辦法了,如果有更好的解決辦法,歡迎留言。
(我整理的解決辦法的參考來源:http://blog.k-res.net/archives/1081.html)

首先對於之前寫的文章自己沒有用真機測試,以及這段時間大家加我的QQ或者私信我也好,有的甚至找到了我的旺旺上,但是我沒有給大家及時回覆很抱歉。


進入正題,我之後又試了下自己寫的方法,發現用模擬器可以,但是真機不可以。

真機卸載後和之前獲取的uuid不同,究其原因就是之前生成的uuid並沒有被keychain成功存儲。


所以問題在keychain.我之前給大家的KeychainItemWrapper類是蘋果官方的,但是有些朋友反映運行時會崩潰。

除此之外,Code Signing Entitlements的創建方法也不夠嚴謹。


所以,請大家重新跟我走一遍吧。



1.新建一個工程,看一下自己的Bundle Id.這個Bundle Id 要和你用真機測試時的證書上面的Bundle Id相匹配。

(絕對有用)iOS獲取UUID,並使用keychain存儲

比如我的是 house.xianrou.xianrou


2.Target - Capabilities - Keychain Sharing - ON


(絕對有用)iOS獲取UUID,並使用keychain存儲

 


(絕對有用)iOS獲取UUID,並使用keychain存儲

這步主要目的是打開Keychain Sharing,將它由灰色狀態的OFF改爲藍色狀態的ON。

打開之後的變化如下:


(絕對有用)iOS獲取UUID,並使用keychain存儲



(絕對有用)iOS獲取UUID,並使用keychain存儲

左側的目錄會自動生成Entitlements文件,不需要自己創建了。


也就是說,Bundle Identifier、Keychain Sharing的Keychain Groups、Entitlements文件的Keychain Access Groups的第一個元素,它們要保持上圖所示的一致性。

設置好了以後可以運行下程序,沒問題可以進行下一步。


3.傳說中的uuid類和keychain類來啦

既然蘋果的keychain方法會崩潰而且有些複雜,我們只保存一個uuid的話可以用下面的簡單方法:

(這也是我自己百度的keychain拷貝別人的,然後改改)

UUID.h

#import  尖括號(Foundation/Foundation.h


@interface UUID : NSObject


+(NSString *)getUUID;


 

@end


UUID.m


#import "UUID.h"

#import "KeyChainStore.h"



@implementation UUID


+(NSString *)getUUID

{

    NSString * strUUID = (NSString *)[KeyChainStore load:@"com.company.app.usernamepassword"];

    

    //首次執行該方法時,uuid爲空

    if ([strUUID isEqualToString:@""] || !strUUID)

    {

        //生成一個uuid的方法

        CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);

        

        strUUID = (NSString *)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault,uuidRef));

   

        //將該uuid保存到keychain

        [KeyChainStore save:KEY_USERNAME_PASSWORD data:strUUID];

        

    }

    return strUUID;

}

 

@end


KeyChainStore.h


#import 尖括號(Foundation/Foundation.h


@interface KeyChainStore : NSObject


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

+ (id)load:(NSString *)service;

+ (void)deleteKeyData:(NSString *)service;

 

@end


KeyChainStore.m

#import "KeyChainStore.h"



@implementation KeyChainStore


+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {

    return [NSMutableDictionary dictionaryWithObjectsAndKeys:

            (id)kSecClassGenericPassword,(id)kSecClass,

            service, (id)kSecAttrService,

            service, (id)kSecAttrAccount,

            (id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,

            nil];

}


+ (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);

}


+ (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;

}


+ (void)deleteKeyData:(NSString *)service {

    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];

    SecItemDelete((CFDictionaryRef)keychainQuery);

}


 

@end


將這兩個類添加到工程中

4.新建一個pch文件,然後pch文件的內容如下:


#ifndef PrefixHeader_pch

#define PrefixHeader_pch


#define  KEY_USERNAME_PASSWORD @"com.company.app.usernamepassword"

#define  KEY_USERNAME @"com.company.app.username"

#define  KEY_PASSWORD @"com.company.app.password"


#endif


pch文件的創建方法可參考:http://blog.csdn.net/huang2009303513/article/details/40375235
有可能會在填Prefix Header 即pch文件的路徑那裏報錯,最近又學習到一種更好的方式$(SRCROOT)/$(PROJECT_NAME)/PrefixHeader.pch,其中$(PROJECT_NAME)是相對工程名,比上面的方法更便捷.


5.在viewcontroller.m裏面執行如下代碼

  NSString * uuid= [UUID getUUID];

  NSLog(@"uuid=%@",uuid);

  得到的uuid類似於這種
  uuid=19AAB430-9CB8-4325-ACC5-D7D386B68960
  
然後卸載掉,再重新運行,看前後得到的uuid是不是一樣吧!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章