iOS 開發(fā) - 設備唯一標志符 - keychain 使用與多個APP之間共享keychain數(shù)據(jù)的使用

這篇攻略是利用keychain來持久化存儲數(shù)據(jù)里初,所以可以利用這個來做設備唯一標志符品擎。另外你只要按照我的步驟操作就沒有什么問題蛮原,簡稱傻瓜式操作~~

1.先來了解一下keychain


這里我想很多人都對yourAppID理解不對,這個yourAppID不是工程的appid创淡,而是在開發(fā)者中心的個人界面就有痴晦;其次yourAppID只對應所使用的開發(fā)者賬號。


2.對這個東西有所理解后琳彩,開始動手操作了誊酌。


把這兩個類文件添加到你項目里,按照我的文件命名即可露乏。


第一個 :

#import@interface KeychainItemWrapper : NSObject

{

NSMutableDictionary *keychainItemData;? ? ? ? // The actual keychain item data backing store.

NSMutableDictionary *genericPasswordQuery;? ? // A placeholder for the generic keychain item query used to locate the item.

}

@property (nonatomic, retain) NSMutableDictionary *keychainItemData;

@property (nonatomic, retain) NSMutableDictionary *genericPasswordQuery;

// Designated initializer.

- (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup;

- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;

- (void)setObject:(id)inObject forKey:(id)key;

- (id)objectForKey:(id)key;

// Initializes and resets the default generic keychain item data.

- (void)resetKeychainItem;

@end


#import "KeychainItemWrapper.h"#import/*

These are the default constants and their respective types,

available for the kSecClassGenericPassword Keychain Item class:

kSecAttrAccessGroup? ? ? ? ? ? -? ? ? ? CFStringRef

kSecAttrCreationDate? ? ? ? -? ? ? ? CFDateRef

kSecAttrModificationDate? ? -? ? ? ? CFDateRef

kSecAttrDescription? ? ? ? ? ? -? ? ? ? CFStringRef

kSecAttrComment? ? ? ? ? ? ? ? -? ? ? ? CFStringRef

kSecAttrCreator? ? ? ? ? ? ? ? -? ? ? ? CFNumberRef

kSecAttrType? ? ? ? ? ? ? ? -? ? ? ? CFNumberRef

kSecAttrLabel? ? ? ? ? ? ? ? -? ? ? ? CFStringRef

kSecAttrIsInvisible? ? ? ? ? ? -? ? ? ? CFBooleanRef

kSecAttrIsNegative? ? ? ? ? ? -? ? ? ? CFBooleanRef

kSecAttrAccount? ? ? ? ? ? ? ? -? ? ? ? CFStringRef

kSecAttrService? ? ? ? ? ? ? ? -? ? ? ? CFStringRef

kSecAttrGeneric? ? ? ? ? ? ? ? -? ? ? ? CFDataRef

See the header file Security/SecItem.h for more details.

*/

@interface KeychainItemWrapper (PrivateMethods)

/*

The decision behind the following two methods (secItemFormatToDictionary and dictionaryToSecItemFormat) was

to encapsulate the transition between what the detail view controller was expecting (NSString *) and what the

Keychain API expects as a validly constructed container class.

*/

- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert;

- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert;

// Updates the item in the keychain, or adds it if it doesn't exist.

- (void)writeToKeychain;

@end

@implementation KeychainItemWrapper

@synthesize keychainItemData, genericPasswordQuery;

- (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup;

{

if (self = [super init])

{

NSAssert(account != nil || service != nil, @"Both account and service are nil.? Must specifiy at least one.");

// Begin Keychain search setup. The genericPasswordQuery the attributes kSecAttrAccount and

// kSecAttrService are used as unique identifiers differentiating keychain items from one another

genericPasswordQuery = [[NSMutableDictionary alloc] init];

[genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

[genericPasswordQuery setObject:account forKey:(id)kSecAttrAccount];

[genericPasswordQuery setObject:service forKey:(id)kSecAttrService];

// The keychain access group attribute determines if this item can be shared

// amongst multiple apps whose code signing entitlements contain the same keychain access group.

if (accessGroup != nil)

{

#if TARGET_IPHONE_SIMULATOR

// Ignore the access group if running on the iPhone simulator.

//

// Apps that are built for the simulator aren't signed, so there's no keychain access group

// for the simulator to check. This means that all apps can see all keychain items when run

// on the simulator.

//

// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the

// simulator will return -25243 (errSecNoAccessForItem).

#else

[genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];

#endif

}

// Use the proper search constants, return only the attributes of the first match.

[genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];

[genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];

NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];

NSMutableDictionary *outDictionary = nil;

if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)

{

// Stick these default values into keychain item if nothing found.

[self resetKeychainItem];

//Adding the account and service identifiers to the keychain

[keychainItemData setObject:account forKey:(id)kSecAttrAccount];

[keychainItemData setObject:service forKey:(id)kSecAttrService];

if (accessGroup != nil)

{

#if TARGET_IPHONE_SIMULATOR

// Ignore the access group if running on the iPhone simulator.

//

// Apps that are built for the simulator aren't signed, so there's no keychain access group

// for the simulator to check. This means that all apps can see all keychain items when run

// on the simulator.

//

// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the

// simulator will return -25243 (errSecNoAccessForItem).

#else

[keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];

#endif

}

}

else

{

// load the saved data from Keychain.

self.keychainItemData = [self secItemFormatToDictionary:outDictionary];

}

[outDictionary release];

}

return self;

}

- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;

{

if (self = [super init])

{

// Begin Keychain search setup. The genericPasswordQuery leverages the special user

// defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain

// items which may be included by the same application.

genericPasswordQuery = [[NSMutableDictionary alloc] init];

[genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrAccount];

// The keychain access group attribute determines if this item can be shared

// amongst multiple apps whose code signing entitlements contain the same keychain access group.

if (accessGroup != nil)

{

#if TARGET_IPHONE_SIMULATOR

// Ignore the access group if running on the iPhone simulator.

//

// Apps that are built for the simulator aren't signed, so there's no keychain access group

// for the simulator to check. This means that all apps can see all keychain items when run

// on the simulator.

//

// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the

// simulator will return -25243 (errSecNoAccessForItem).

#else

[genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];

#endif

}

// Use the proper search constants, return only the attributes of the first match.

[genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];

[genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];

NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];

NSMutableDictionary *outDictionary = nil;

if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)

{

// Stick these default values into keychain item if nothing found.

[self resetKeychainItem];

// Add the generic attribute and the keychain access group.

[keychainItemData setObject:identifier forKey:(id)kSecAttrAccount];

if (accessGroup != nil)

{

#if TARGET_IPHONE_SIMULATOR

// Ignore the access group if running on the iPhone simulator.

//

// Apps that are built for the simulator aren't signed, so there's no keychain access group

// for the simulator to check. This means that all apps can see all keychain items when run

// on the simulator.

//

// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the

// simulator will return -25243 (errSecNoAccessForItem).

#else

[keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];

#endif

}

}

else

{

// load the saved data from Keychain.

self.keychainItemData = [self secItemFormatToDictionary:outDictionary];

}

[outDictionary release];

}

return self;

}

- (void)dealloc

{

[keychainItemData release];

[genericPasswordQuery release];

[super dealloc];

}

- (void)setObject:(id)inObject forKey:(id)key

{

if (inObject == nil) return;

id currentObject = [keychainItemData objectForKey:key];

if (![currentObject isEqual:inObject])

{

[keychainItemData setObject:inObject forKey:key];

[self writeToKeychain];

}

}

- (id)objectForKey:(id)key

{

return [keychainItemData objectForKey:key];

}

- (void)resetKeychainItem

{

OSStatus junk = noErr;

if (!keychainItemData)

{

self.keychainItemData = [[NSMutableDictionary alloc] init];

}

else if (keychainItemData)

{

NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData];

junk = SecItemDelete((CFDictionaryRef)tempDictionary);

NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." );

}

// Default attributes for keychain item.

[keychainItemData setObject:@"" forKey:(id)kSecAttrAccount];

[keychainItemData setObject:@"" forKey:(id)kSecAttrLabel];

[keychainItemData setObject:@"" forKey:(id)kSecAttrDescription];

// Default data for keychain item.

[keychainItemData setObject:@"" forKey:(id)kSecValueData];

}

- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert

{

// The assumption is that this method will be called with a properly populated dictionary

// containing all the right key/value pairs for a SecItem.

// Create a dictionary to return populated with the attributes and data.

NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];

// Add the Generic Password keychain item class attribute.

[returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

// Convert the NSString to NSData to meet the requirements for the value type kSecValueData.

// This is where to store sensitive data that should be encrypted.

NSString *passwordString = [dictionaryToConvert objectForKey:(id)kSecValueData];

[returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData];

return returnDictionary;

}

- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert

{

// The assumption is that this method will be called with a properly populated dictionary

// containing all the right key/value pairs for the UI element.

// Create a dictionary to return populated with the attributes and data.

NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];

// Add the proper search key and class attribute.

[returnDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];

[returnDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

// Acquire the password data from the attributes.

NSData *passwordData = NULL;

if (SecItemCopyMatching((CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr)

{

// Remove the search, class, and identifier key/value, we don't need them anymore.

[returnDictionary removeObjectForKey:(id)kSecReturnData];

// Add the password to the dictionary, converting from NSData to NSString.

NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length]

encoding:NSUTF8StringEncoding] autorelease];

[returnDictionary setObject:password forKey:(id)kSecValueData];

}

else

{

// Don't do anything if nothing is found.

NSAssert(NO, @"Serious error, no matching item found in the keychain.\n");

}

[passwordData release];

return returnDictionary;

}

- (void)writeToKeychain

{

NSDictionary *attributes = NULL;

NSMutableDictionary *updateItem = NULL;

OSStatus result;

if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr)

{

// First we need the attributes from the Keychain.

updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes];

// Second we need to add the appropriate search key/values.

[updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(id)kSecClass];

// Lastly, we need to set up the updated attribute list being careful to remove the class.

NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData];

[tempCheck removeObjectForKey:(id)kSecClass];

#if TARGET_IPHONE_SIMULATOR

// Remove the access group if running on the iPhone simulator.

//

// Apps that are built for the simulator aren't signed, so there's no keychain access group

// for the simulator to check. This means that all apps can see all keychain items when run

// on the simulator.

//

// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the

// simulator will return -25243 (errSecNoAccessForItem).

//

// The access group attribute will be included in items returned by SecItemCopyMatching,

// which is why we need to remove it before updating the item.

[tempCheck removeObjectForKey:(id)kSecAttrAccessGroup];

#endif

// An implicit assumption is that you can only update a single item at a time.

result = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck);

NSAssert( result == noErr, @"Couldn't update the Keychain Item." );

}

else

{

// No previous item found; add the new one.

result = SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);

NSAssert( result == noErr, @"Couldn't add the Keychain Item." );

}

}

@end

```


第二個:

```

#import@interface AppUntils : NSObject

+(void)saveUUIDToKeyChain;

+(NSString *)readUUIDFromKeyChain;

+ (NSString *)getUUIDString;

@end


#import "AppUntils.h"#import#import "KeychainItemWrapper.h"

@implementation AppUntils

#pragma mark - 保存和讀取UUID

+(void)saveUUIDToKeyChain{

KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithAccount:@"Identfier" service:@"AppName" accessGroup:nil];

NSString *string = [keychainItem objectForKey: (__bridge id)kSecAttrGeneric];

if([string isEqualToString:@""] || !string){

[keychainItem setObject:[self getUUIDString] forKey:(__bridge id)kSecAttrGeneric];

}

}

+(NSString *)readUUIDFromKeyChain{

KeychainItemWrapper *keychainItemm = [[KeychainItemWrapper alloc] initWithAccount:@"Identfier" service:@"AppName" accessGroup:nil];

NSString *UUID = [keychainItemm objectForKey: (__bridge id)kSecAttrGeneric];

return UUID;

}

+ (NSString *)getUUIDString

{

CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);

CFStringRef strRef = CFUUIDCreateString(kCFAllocatorDefault , uuidRef);

NSString *uuidString = [(__bridge NSString*)strRef stringByReplacingOccurrencesOfString:@"-" withString:@""];

CFRelease(strRef);

CFRelease(uuidRef);

return uuidString;

}

@end




3.因為KeychainItemWrapper.m文件是在非ARC環(huán)境下運行的碧浊,所以需要設置非arc編譯環(huán)境,在tag下設置瘟仿,如圖所示:

4.在項目相同的目錄下創(chuàng)建KeychainAccessGroups.plist文件箱锐,不是在info.plist里。

按照圖里要求填寫劳较,前綴是appid驹止,我自己瞎寫的一個浩聋,你需要用對應你的開發(fā)者賬號的來弄,還有公司名幢哨。


5.大功告成咯~~我們就隨便用用看吧~~


添加兩個頭文件赡勘,這個方法就能獲取到UUID

要取出來,用 ?readUUIDFromKeyChain ?即可捞镰。


最后

有時候我們會發(fā)現(xiàn)有些app比較賴皮闸与,卸載后再裝竟然不需要登錄就能上去,這個大多數(shù)原因也是因為他們把用戶數(shù)據(jù)保存在了 keychain 里岸售;另外如果涉及到安全性較高的践樱,即使有兩款產品,我們也不會建議讓他們使用同一個 keychain凸丸。當然拷邢,如果只是采集一些用戶行為數(shù)據(jù)的話,這個還是可以的



參考文章的鏈接 :

獲取iOS設備唯一標示UUID

iOS 開發(fā)keychain 使用與多個APP之間共享keychain數(shù)據(jù)的使用

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末屎慢,一起剝皮案震驚了整個濱河市瞭稼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腻惠,老刑警劉巖环肘,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異集灌,居然都是意外死亡悔雹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門欣喧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腌零,“玉大人,你說我怎么就攤上這事唆阿∫娼В” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵驯鳖,是天一觀的道長饰躲。 經常有香客問我,道長臼隔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任妄壶,我火速辦了婚禮摔握,結果婚禮上,老公的妹妹穿的比我還像新娘丁寄。我一直安慰自己氨淌,他們只是感情好泊愧,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盛正,像睡著了一般删咱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上豪筝,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天痰滋,我揣著相機與錄音,去河邊找鬼续崖。 笑死敲街,一個胖子當著我的面吹牛,可吹牛的內容都是我干的严望。 我是一名探鬼主播多艇,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼像吻!你這毒婦竟也來了峻黍?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤拨匆,失蹤者是張志新(化名)和其女友劉穎姆涩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涮雷,經...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡阵面,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了洪鸭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片样刷。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖览爵,靈堂內的尸體忽然破棺而出置鼻,到底是詐尸還是另有隱情,我是刑警寧澤蜓竹,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布箕母,位于F島的核電站,受9級特大地震影響俱济,放射性物質發(fā)生泄漏嘶是。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一蛛碌、第九天 我趴在偏房一處隱蔽的房頂上張望聂喇。 院中可真熱鬧,春花似錦、人聲如沸希太。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽誊辉。三九已至矾湃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堕澄,已是汗流浹背邀跃。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奈偏,地道東北人坞嘀。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像惊来,于是被迫代替她去往敵國和親丽涩。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內容