好的,我在这篇博文中找到了解决方案Keychain duplicate item when added password
总而言之,问题在于 GenericKeychain 示例应用程序使用存储在 kSecAttrGeneric 键中的值作为钥匙串项的标识符,而实际上这不是 API 用来确定唯一钥匙串项的标识符。您需要设置唯一值的键是 kSecAttrAccount 键和/或 kSecAttrService 键。
您可以重写 KeychainItemWrapper 的初始化程序,因此您不需要通过更改这些行来更改任何其他代码:
改变:
[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];
至:
[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrAccount];
并改变:
[keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric];
至:
[keychainItemData setObject:identifier forKey:(id)kSecAttrAccount];
或者,您可以像我所做的那样编写一个新的初始化程序,它需要两个识别键:
编辑:对于使用 ARC 的人(你现在应该是)检查nycynik 的答案以获得所有正确的桥接符号
- (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;
}
希望这对其他人有所帮助!