问题
目前需要保存一些用户配置,在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