開(kāi)發(fā)過(guò)程中,我們都會(huì)使用Keychain或者UserDefaults來(lái)存儲(chǔ)一些App登錄用戶相關(guān)的數(shù)據(jù),或者服務(wù)器的token等,可以很方便的實(shí)現(xiàn)App啟動(dòng)自登錄的功能;但是這個(gè)對(duì)于測(cè)試來(lái)說(shuō)就比較頭疼,尤其是存儲(chǔ)到Keychain里的不能刪除的數(shù)據(jù),而要模仿一個(gè)全新的從沒(méi)有安裝過(guò)我們應(yīng)用的App的時(shí)候,必須要清除掉保存的Keychain。
1. Keychain清除
Keychain是iOS設(shè)備中的一個(gè)安全的存儲(chǔ)容器,可以用來(lái)為應(yīng)用保存敏感信息比如用戶名,密碼,網(wǎng)絡(luò)密碼,認(rèn)證令牌。在刪掉應(yīng)用后,Keychain中的數(shù)據(jù)也會(huì)保留,用戶再次安裝的App還能從Keychain中讀取到數(shù)據(jù)。所以Keychain中保存的數(shù)據(jù)一般都是比較重要,并且不希望卸載應(yīng)用后就被刪除的數(shù)據(jù),比如用戶的登錄信息,wifi密碼等。我們常用的OpenUDID庫(kù)就是使用Keychain的這個(gè)特性來(lái)獲取唯一性的設(shè)備ID,具體做法是,生成一個(gè)UUID,或者是獲取廣告標(biāo)識(shí)符,然后存入Keychain,這樣刪除應(yīng)用后再次安裝應(yīng)用也還能讀取到上一次生成的設(shè)備標(biāo)識(shí)符。
添加操作
+ (void)save:(NSString*)service data:(NSData*)data
{
// OSStatus ret = 100;
NSMutableDictionary * keyChainDic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge_transfer id)kSecClassGenericPassword, (__bridge_transfer id)kSecClass,
service, (__bridge_transfer id)kSecAttrService,
service, (__bridge_transfer id)kSecAttrAccount, nil];
if (noErr != SecItemDelete((__bridge_retained CFDictionaryRef)keyChainDic))
{
DDMLogError(KEY_CHAIN, @"save : SecItemDelete failed!");
}
[keyChainDic setObject:[NSKeyedArchiver archivedDataWithRootObject:data]
forKey:(__bridge_transfer id)kSecValueData];
if (noErr != SecItemAdd((__bridge_retained CFDictionaryRef)keyChainDic, NULL))
{
DDMLogError(KEY_CHAIN, @"save : SecItemAdd failed!");
}
}
指定讀取
+ (NSData*)load:(NSString*)service
{
NSData * ret = nil;
NSMutableDictionary * keyChainDic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge_transfer id)kSecClassGenericPassword, (__bridge_transfer id)kSecClass,
service, (__bridge_transfer id)kSecAttrService,
service, (__bridge_transfer id)kSecAttrAccount, nil];
[keyChainDic setObject:(__bridge_transfer id)kCFBooleanTrue
forKey:(__bridge_transfer id)kSecReturnData];
[keyChainDic setObject:(__bridge_transfer id)kSecMatchLimitOne
forKey:(__bridge_transfer id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (noErr == SecItemCopyMatching((__bridge_retained CFDictionaryRef)keyChainDic,
(CFTypeRef*)&keyData))
{
@try
{
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge_transfer NSData *)keyData];
}
@catch (NSException *e)
{
NSLog(@"Unarchive of %@ failed: %@", service, e);
}
@finally
{
}
}
return ret;
}
清空Keychain的思路很簡(jiǎn)單,就是遍歷keychain中存儲(chǔ)的數(shù)據(jù),然后挨個(gè)刪除。代碼如下:
1.全部刪除
- (void)clearKeyChain {
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnAttributes,
(__bridge id)kSecMatchLimitAll, (__bridge id)kSecMatchLimit,
nil];
NSArray *secItemClasses = [NSArray arrayWithObjects:
(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecClassInternetPassword,
(__bridge id)kSecClassCertificate,
(__bridge id)kSecClassKey,
(__bridge id)kSecClassIdentity,
nil];
for (id secItemClass in secItemClasses) {
[query setObject:secItemClass forKey:(__bridge id)kSecClass];
CFTypeRef result = NULL;
SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
if (result != NULL) CFRelease(result);
NSDictionary *spec = @{(__bridge id)kSecClass: secItemClass};
SecItemDelete((__bridge CFDictionaryRef)spec);
}
}
2.指定刪除
+ (void)delete:(NSString *)service {
NSMutableDictionary * keyChainDic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge_transfer id)kSecClassGenericPassword, (__bridge_transfer id)kSecClass,
service, (__bridge_transfer id)kSecAttrService,
service, (__bridge_transfer id)kSecAttrAccount, nil];
if (noErr != SecItemDelete((__bridge_retained CFDictionaryRef)keyChainDic))
{
DDMLogError(KEY_CHAIN, @"delete : SecItemDelete failed!");
}
}
2. NSUserDefault的清除
UserDefaults是iOS設(shè)備上一個(gè)Key-Value的存儲(chǔ)容器,最終數(shù)據(jù)會(huì)以plist文件的形式存在文件夾Library/Preferences下面。默認(rèn)的UserDefaults的文件名是bundle identitier加上.plist的后綴名。UserDefaulst會(huì)隨App的刪除而被清除(也可以同步到iCloud上),所以UserDefaults存儲(chǔ)著一些可以再生的的數(shù)據(jù)。
- (void)clearUserDefaults {
// 清空默認(rèn)存儲(chǔ)
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
[[NSUserDefaults standardUserDefaults] synchronize];
// 清空自定義存儲(chǔ)
NSString *path = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).lastObject;
path = [path stringByAppendingPathComponent:@"Preferences"];
NSArray *fileList = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
for (NSString * filename in fileList) {
NSString *filepath = [path stringByAppendingPathComponent:filename];
BOOL isDir = NO;
[[NSFileManager defaultManager] fileExistsAtPath:filepath isDirectory:(&isDir)];
if (!isDir && [filename hasSuffix:@".plist"] && (![filename isEqualToString:appDomain])) {
NSString *suitename = [filename stringByDeletingPathExtension];
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:suitename];
[userDefaults removePersistentDomainForName:suiteName];
[[NSFileManager defaultManager] removeItemAtPath:filepath error:nil];
}
}
}