問題
目前需要保存一些用戶配置,在app刪掉後依然能正常讀取,那麼就用到【鑰匙串】
鑰匙串簡介
項目中有時會需要存儲敏感信息(如密碼、密鑰等),蘋果官方提供了一種存儲機制--鑰匙串(keychain)。
keychain是一種存儲在硬盤上的加密的數據庫。這個可能是卸載App後,keychain信息還在的原因。
keychain適合存儲 較小的數據量(不超過上千字節或上兆字節)的內容。
解決方案
JJKeychain.h
@interface JJKeychain : NSObject
//保存是先刪掉之前的key,沒有使用update,感覺這樣簡單;然後保存的value轉換爲NSData,如果value爲自定義object,則需遵循NSSecureCoding協議
+ (BOOL)setValue:(id)value forKey:(NSString *)key;
+ (BOOL)setValue:(id)value forKey:(NSString *)key forAccessGroup:(nullable NSString *)group;
+ (id)valueForKey:(NSString *)key;
+ (id)valueForKey:(NSString *)key forAccessGroup:(nullable NSString *)group;
+ (BOOL)deleteValueForKey:(NSString *)key;
+ (BOOL)deleteValueForKey:(NSString *)key forAccessGroup:(nullable NSString *)group;
+ (NSString *)getBundleSeedIdentifier;
@end
JJKeychain.m
@implementation JJKeychain
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)key forAccessGroup:(NSString *)group{
NSMutableDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService : key,
(__bridge id)kSecAttrAccount : key,
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleAfterFirstUnlock
}.mutableCopy;
if (group != nil) {
[query setObject:[self getFullAccessGroup:group] forKey:(__bridge id)kSecAttrAccessGroup];
}
return query;
}
+ (NSString *)getFullAccessGroup:(NSString *)group
{
NSString *accessGroup = nil;
NSString *bundleSeedIdentifier = [self getBundleSeedIdentifier];
if (bundleSeedIdentifier != nil && [group rangeOfString:bundleSeedIdentifier].location == NSNotFound) {
accessGroup = [NSString stringWithFormat:@"%@.%@", bundleSeedIdentifier, group];
}
return accessGroup;
}
+ (NSString *)getBundleSeedIdentifier
{
static __strong NSString *bundleSeedIdentifier = nil;
if (bundleSeedIdentifier == nil) {
@synchronized(self) {
if (bundleSeedIdentifier == nil) {
NSString *_bundleSeedIdentifier = nil;
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: @"bundleSeedID",
(__bridge id)kSecAttrService: @"",
(__bridge id)kSecReturnAttributes: (__bridge id)kCFBooleanTrue
};
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecItemNotFound) {
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
}
if (status == errSecSuccess) {
NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:(__bridge NSString *)kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:@"."];
// NSLog(@"components %@",components);
_bundleSeedIdentifier = [[components objectEnumerator] nextObject];
CFRelease(result);
}
if (_bundleSeedIdentifier != nil) {
bundleSeedIdentifier = [_bundleSeedIdentifier copy];
}
}
}
}
return bundleSeedIdentifier;
}
+ (BOOL)setValue:(id)value forKey:(NSString *)key{
return [self setValue:value forKey:key forAccessGroup:nil];
}
+ (BOOL)setValue:(id)value forKey:(NSString *)key forAccessGroup:(NSString *)group{
NSMutableDictionary *query = [self getKeychainQuery:key forAccessGroup:group];
[self deleteValueForKey:key forAccessGroup:group];
NSData *data = nil;
@try {
data = [NSKeyedArchiver archivedDataWithRootObject:value];
} @catch (NSException *exception) {
NSLog(@"archived failure value %@ %@",value,exception);
return NO;
}
[query setObject:data forKey:(__bridge id)kSecValueData];
OSStatus result = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
return result == errSecSuccess;
}
+ (BOOL)deleteValueForKey:(NSString *)key{
return [self deleteValueForKey:key forAccessGroup:nil];
}
+ (BOOL)deleteValueForKey:(NSString *)key forAccessGroup:(NSString *)group{
NSMutableDictionary *query = [self getKeychainQuery:key forAccessGroup:group];
OSStatus result = SecItemDelete((__bridge CFDictionaryRef)query);
return result == errSecSuccess;
}
+ (id)valueForKey:(NSString *)key{
return [self valueForKey:key forAccessGroup:nil];
}
+ (id)valueForKey:(NSString *)key forAccessGroup:(NSString *)group{
id value = nil;
NSMutableDictionary *query = [self getKeychainQuery:key forAccessGroup:group];
CFDataRef keyData = NULL;
[query setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[query setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
if (SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&keyData) == errSecSuccess) {
@try {
value = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
}
@catch (NSException *e) {
NSLog(@"Unarchive of %@ failed: %@", key, e);
value = nil;
}
}
if (keyData) {
CFRelease(keyData);
}
return value;
}
@end