今天做一個需求束铭,要求獲取到設(shè)備的IDFA,但是我們都知道蚌堵,這個值是會變的买决,會受到用戶的影響,所以就想看能不能做一個持久化(對于用戶不允許的情況吼畏,可以自己生成一個督赤,實現(xiàn)有具體的開源代碼),隨著軟件的卸載和再安裝泻蚊,這個值始終是不變的躲舌。
就想起來了keychain--這個系統(tǒng)級的存儲。這也就是為什么有些軟件在卸載后重新安裝后用戶名和密碼還在的方法之一(因為還有其他方法)性雄。
除了做數(shù)據(jù)存儲没卸,其實它還可以做APP間的數(shù)據(jù)共享。
因為這部分概念的東西并不多秒旋,所以下面將直接上代碼约计,遇到問題順帶著再說;另:mac和iOS不同迁筛,這里只說iOS的煤蚌。
所有的詳細資料在這里。
一细卧、Keychain
1尉桩、概念
Keychain是一個儲存在文件下的簡單數(shù)據(jù)庫。通常情況下贪庙,app里有一個簡單的keychain蜘犁,可以被所有的app使用。
Keychain有任意數(shù)量的鑰匙鏈(item)插勤,該鑰匙鏈里包含一組屬性沽瘦。該屬性和鑰匙鏈的類型相關(guān)革骨。創(chuàng)建日期和label對所有的鑰匙鏈?zhǔn)峭ㄓ玫摹F渌亩际歉鶕?jù)鑰匙鏈的類型不同而不同析恋,比如良哲,generic password
類型包含service和account屬性。
鑰匙鏈可以使用kSecAttrSynchronizable
同步屬性助隧,被標(biāo)記為該屬性的值都可以被放置在iCloud的鑰匙鏈中筑凫,它會被自動同步到相同賬號的設(shè)備上。
有些鑰匙鏈需要保護起來并村,比如密碼和私人key巍实,都會被加密;對于那些不需要被保護的鑰匙鏈哩牍,比如證書棚潦,就不會被加密。
在iOS設(shè)備上(手機)膝昆,當(dāng)屏幕被解鎖時丸边,鑰匙鏈的訪問權(quán)限就會被打開。
2荚孵、訪問
首先說明其能保存的類型妹窖,有5種:
- kSecClassGenericPassword:存儲一般密碼,比較常用這個
- kSecClassInternetPassword:存儲網(wǎng)絡(luò)密碼
- kSecClassCertificate:存儲證書
- kSecClassKey:存儲私有密鑰
- kSecClassIdentity:存儲一個包含證書和私有密鑰的item
(1)添加
- 方法:
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
??attributes有四部分組成:
??a收叶、item的類型骄呼,必選,key=kSecClass,value就是上面說的5種;
??b判没、item的存儲數(shù)據(jù)蜓萄;
??c、屬性哆致,可以用來做一些標(biāo)記用于查找或者程序之間的數(shù)據(jù)分享绕德,但是不同的kSecClass有不同的屬性;
??d摊阀、返回數(shù)據(jù)類型耻蛇,它的設(shè)置影響參數(shù)result。
??result:對添加的item的引用胞此,如果沒有什么需要臣咖,可以設(shè)為nil - 使用:
- (void)saveBasicInfo {
CFMutableDictionaryRef mutableDicRef = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(mutableDicRef, kSecClass, kSecClassGenericPassword); //類型
CFDateRef dateRef = CFDateCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent());
CFDictionarySetValue(mutableDicRef, kSecAttrCreationDate, dateRef); //創(chuàng)建時間
CFStringRef strRef = CFSTR("save generic password 2");
CFDictionarySetValue(mutableDicRef, kSecAttrDescription, strRef); //描述
CFStringRef commentStrRef = CFSTR("generic password comment 2");
CFDictionarySetValue(mutableDicRef, kSecAttrComment, commentStrRef); //備注、注釋
CFNumberRef creatorRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberCharType, "zhou");
CFDictionarySetValue(mutableDicRef, kSecAttrCreator, creatorRef); //創(chuàng)建者漱牵,只能是四個字符長度
CFNumberRef typeRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberCharType, "type");
CFDictionarySetValue(mutableDicRef, kSecAttrType, typeRef); // 類型
CFStringRef labelRef = CFSTR("label 2");
CFDictionarySetValue(mutableDicRef, kSecAttrLabel, labelRef); //標(biāo)簽夺蛇,用戶可以看到
CFDictionarySetValue(mutableDicRef, kSecAttrIsInvisible, kCFBooleanTrue); //是否不可見、是否隱藏(kCFBooleanTrue酣胀、kCFBooleanFalse)
CFDictionarySetValue(mutableDicRef, kSecAttrIsNegative, kCFBooleanFalse); //標(biāo)記是否有密碼(kCFBooleanTrue刁赦、kCFBooleanFalse)
CFStringRef accountRef = CFSTR("zhoupengzu_basic 2");
CFDictionarySetValue(mutableDicRef, kSecAttrAccount, accountRef); //賬戶娶聘,相同的賬戶不允許儲存兩次,否則會報錯
CFStringRef serviceRef = CFSTR("service");
CFDictionarySetValue(mutableDicRef, kSecAttrService, serviceRef); //所具有的服務(wù)
CFMutableDataRef genericRef = CFDataCreateMutable(kCFAllocatorDefault, 0);
char * generic_char = "personal generic 2";
CFDataAppendBytes(genericRef, (const UInt8 *)generic_char, sizeof("personal generic 2"));
CFDictionarySetValue(mutableDicRef, kSecAttrGeneric, genericRef); //用戶自定義
CFDictionarySetValue(mutableDicRef, kSecValueData, genericRef); //保存值
OSStatus status = SecItemAdd(mutableDicRef, nil); //相同的東西只能添加一次甚脉,不能重復(fù)添加丸升,重復(fù)添加會報錯
if (status == errSecSuccess) {
NSLog(@"success");
} else {
NSLog(@"%@",@(status));
}
... //神略的是前面創(chuàng)建的變量的釋放
}
- 總結(jié):
??1、要保證每次添加都是不同賬戶
??2牺氨、注意變量內(nèi)存釋放
??3狡耻、要注意數(shù)據(jù)類型要對
(2)查找(最好的辦法是根據(jù)用戶名查找)
- 方法:
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
??query的組成:
??a、item的類型猴凹,必選夷狰,key=kSecClass,value就是上面說的5種;
??b、要查找的屬性以及值郊霎,這個可以用來篩選掉多余的選項沼头,這個值越詳細,最后獲取的結(jié)果越精確
??c歹篓、做進一步的限制瘫证,比如大小寫是否敏感等等
??d、返回匹配項的個數(shù)庄撮,但是注意kSecReturnData
和kSecMatchLimit不能共存,更多的kSecMatchLimit可以搭配kSecReturnAttributes使用 - 使用:
- (OSStatus)keychainMatching {
CFMutableDictionaryRef queryDic = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
//查找類型
CFDictionarySetValue(queryDic, kSecClass, kSecClassGenericPassword);
//匹配的屬性 越詳細毙籽,越精準(zhǔn)
// CFStringRef strRef = CFSTR("save generic password");
// CFDictionarySetValue(queryDic, kSecAttrDescription, strRef); //描述
// CFStringRef commentStrRef = CFSTR("generic password comment");
// CFDictionarySetValue(queryDic, kSecAttrComment, commentStrRef); //備注洞斯、注釋
// CFNumberRef creatorRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberCharType, "zhou");
// CFDictionarySetValue(queryDic, kSecAttrCreator, creatorRef); //創(chuàng)建者,只能是四個字符長度
// CFNumberRef typeRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberCharType, "type");
// CFDictionarySetValue(queryDic, kSecAttrType, typeRef); // 類型
// CFStringRef labelRef = CFSTR("label");
// CFDictionarySetValue(queryDic, kSecAttrLabel, labelRef); //標(biāo)簽坑赡,用戶可以看到
//查找的參數(shù)
// CFDictionarySetValue(queryDic, kSecMatchLimit, kSecMatchLimitAll); //可以控制當(dāng)key=kSecReturnAttributes時返回值的個數(shù)
//返回類型
// CFDictionarySetValue(queryDic, kSecReturnData, kCFBooleanTrue);
CFDictionarySetValue(queryDic, kSecReturnAttributes, kCFBooleanTrue);
CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching(queryDic, &result);
if (status == errSecSuccess) {
NSLog(@"success:%@",result);
if (CFGetTypeID(result) == CFDictionaryGetTypeID()) { //類型判斷
NSLog(@"Dictionary");
}
} else {
NSLog(@"%@",@(status));
}
...//神略的是前面創(chuàng)建的變量的釋放
}
- 總結(jié):
這里主要注意一下返回類型就好了@尤纭!毅否!
(3)刪除
- 方法:
OSStatus SecItemDelete(CFDictionaryRef query)
query可以直接自己創(chuàng)建也可以是查找后獲得的亚铁,具體使用看下面的使用。 - 使用:
方法一:
//直接刪除
- (void)deleteItemDirect {
//刪除CFStringRef accountRef = CFSTR("zhoupengzu_basic 2");
CFMutableDictionaryRef queryDic = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(queryDic, kSecClass, kSecClassGenericPassword); //這個不要丟C印E且纭!
CFStringRef accountRef = CFSTR("zhoupengzu_basic 2");
CFDictionarySetValue(queryDic, kSecAttrAccount, accountRef);
if (accountRef) {
CFRelease(accountRef);
}
OSStatus delStatus = SecItemDelete(queryDic);
if (delStatus == errSecSuccess) {
NSLog(@"delete success");
} else {
NSLog(@"%@",@(delStatus));
}
}
方法二:
//先查找捆探,再刪除
- (void)deleteItemWithQuery {
CFMutableDictionaryRef queryDic = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(queryDic, kSecClass, kSecClassGenericPassword);
CFStringRef accountRef = CFSTR("zhoupengzu_basic 2");
CFDictionarySetValue(queryDic, kSecAttrAccount, accountRef);
if (accountRef) {
CFRelease(accountRef);
}
CFDictionarySetValue(queryDic, kSecReturnAttributes, kCFBooleanTrue);
CFDictionarySetValue(queryDic, kSecReturnRef, kCFBooleanTrue); //這一句話必須要有然爆,否則刪除不了
// CFDictionarySetValue(queryDic, kSecReturnPersistentRef, kCFBooleanTrue); //不能用這句,這句是做什么用的呢黍图?
CFTypeRef result = NULL;
OSStatus queryStatus = SecItemCopyMatching(queryDic, &result);
if (queryDic) {
CFRelease(queryDic);
}
if (queryStatus != errSecSuccess) {
NSLog(@"query failed");
return;
}
if (CFGetTypeID(result) == CFDictionaryGetTypeID()) {
OSStatus delStatus = SecItemDelete(result);
if (delStatus == errSecSuccess) {
NSLog(@"delete success");
} else {
NSLog(@"delete failed:%@",@(delStatus));
}
} else if (CFGetTypeID(result) == CFArrayGetTypeID()) {
CFArrayRef arrRef = result;
for (CFIndex i = 0; i < CFArrayGetCount(arrRef); i++) {
OSStatus delStatus = SecItemDelete(CFArrayGetValueAtIndex(arrRef, i));
if (delStatus == errSecSuccess) {
NSLog(@"delete success");
} else {
NSLog(@"delete failed:%@",@(delStatus));
}
}
}
}
- 總結(jié):
1曾雕、不要忘記設(shè)置item的類型
2、如果是先查找然后再刪除助被,則需要加上kSecReturnRef或者kSecReturnPersistentRef(這個還沒搞懂)
(4)剖张、更新
- 方法:
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate)
attributesToUpdate:包含需要去更新的
在使用上切诀,同樣有可以直接定義,然后去更新(其實其在內(nèi)部先查找了)搔弄;也可以先查找幅虑,再把查找到的作為參數(shù)去更新。 - 使用:
方法一:
//更新
- (void)updateItem {
CFMutableDictionaryRef queryDic = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(queryDic, kSecClass, kSecClassGenericPassword);
CFStringRef accountRef = CFSTR("zhoupengzu_basic 2");
CFDictionarySetValue(queryDic, kSecAttrAccount, accountRef);
if (accountRef) {
CFRelease(accountRef);
}
CFMutableDictionaryRef updateDic = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFStringRef strRef = CFSTR("save generic password update");
CFDictionarySetValue(updateDic, kSecAttrDescription, strRef); //描述
OSStatus updateStatus = SecItemUpdate(queryDic, updateDic);
if (updateStatus == errSecSuccess) {
NSLog(@"success");
} else {
NSLog(@"update Failed:%@",@(updateStatus));
}
CFRelease(queryDic);
CFRelease(updateDic);
}
方法二:
- (void)updateItemAfterSearch {
CFMutableDictionaryRef queryDic = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(queryDic, kSecClass, kSecClassGenericPassword);
CFStringRef accountRef = CFSTR("zhoupengzu_basic 2");
CFDictionarySetValue(queryDic, kSecAttrAccount, accountRef);
if (accountRef) {
CFRelease(accountRef);
}
CFDictionarySetValue(queryDic, kSecReturnAttributes, kCFBooleanTrue);
CFDictionarySetValue(queryDic, kSecReturnRef, kCFBooleanTrue); //這一句話必須要有肯污,否則刪除不了
// CFDictionarySetValue(queryDic, kSecReturnPersistentRef, kCFBooleanTrue); //不能用這句翘单,這句是做什么用的呢?
CFTypeRef result = NULL;
OSStatus queryStatus = SecItemCopyMatching(queryDic, &result);
if (queryDic) {
CFRelease(queryDic);
}
if (queryStatus != errSecSuccess) {
NSLog(@"query failed");
return;
}
if (CFGetTypeID(result) != CFDictionaryGetTypeID()) {
return;
}
CFMutableDictionaryRef updateDic = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFStringRef strRef = CFSTR("save generic password update with query");
CFDictionarySetValue(updateDic, kSecAttrDescription, strRef); //描述
OSStatus updateStatus = SecItemUpdate(result, updateDic);
if (updateStatus == errSecSuccess) {
NSLog(@"success");
} else {
NSLog(@"update Failed:%@",@(updateStatus));
}
CFRelease(updateDic);
}
(5)獲取值
- 返回屬性kSecReturnAttributes
屬性的返回都是以集合形式返回蹦渣,即要么是數(shù)組+字典哄芜,或者就只有字典(默認),所以只要確定了返回類型柬唯,然后按照key讀取就可以了认臊。如下:
- (OSStatus)keychainMatching {
CFMutableDictionaryRef queryDic = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
//查找類型
CFDictionarySetValue(queryDic, kSecClass, kSecClassGenericPassword);
CFStringRef accountRef = CFSTR("zhoupengzu_basic 2");
CFDictionarySetValue(queryDic, kSecAttrAccount, accountRef); //賬戶
//查找的參數(shù)
CFDictionarySetValue(queryDic, kSecMatchLimit, kSecMatchLimitOne); //可以控制當(dāng)key=kSecReturnAttributes時返回值的個數(shù)
//返回類型
CFDictionarySetValue(queryDic, kSecReturnAttributes, kCFBooleanTrue);
CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching(queryDic, &result);
if (status == errSecSuccess) {
NSLog(@"success:%@",result);
if (CFGetTypeID(result) == CFDictionaryGetTypeID()) { //類型判斷
NSLog(@"label:%@",CFDictionaryGetValue(result, kSecAttrDescription));
}
} else {
NSLog(@"%@",@(status));
}
if (queryDic) {
CFRelease(queryDic);
}
return status;
}
- 返回值為kSecReturnData
- (OSStatus)keychainMatching {
CFMutableDictionaryRef queryDic = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
//查找類型
CFDictionarySetValue(queryDic, kSecClass, kSecClassGenericPassword);
CFStringRef accountRef = CFSTR("zhoupengzu_basic 2");
CFDictionarySetValue(queryDic, kSecAttrAccount, accountRef); //賬戶
//查找的參數(shù)
//返回類型
CFDictionarySetValue(queryDic, kSecReturnData, kCFBooleanTrue);
CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching(queryDic, &result);
if (status == errSecSuccess) {
NSLog(@"success:%@",result);
if (CFGetTypeID(result) == CFDictionaryGetTypeID()) { //類型判斷
NSLog(@"label:%@",CFDictionaryGetValue(result, kSecAttrDescription));
} else if (CFGetTypeID(result) == CFDataGetTypeID()) {
const UInt8 * str = CFDataGetBytePtr(result);
NSLog(@"%s",str);
}
} else {
NSLog(@"%@",@(status));
}
if (queryDic) {
CFRelease(queryDic);
}
return status;
}
- 其他(kSecReturnRef和kSecReturnPersistentRef)
這兩個我暫時還沒搞懂,搞懂了補上3荨J纭!