iOS Keychain更新邮箱信息需两次点击才生效问题求助
iOS Swift Keychain登录信息更新异常排查与解决
问题现象
在iOS开发中使用Swift操作Keychain存储登录信息时出现以下异常:
- 无已存凭据时,
save函数可正常保存登录信息 - 仅更新密码时,
update函数可正常修改密码 - 已存登录信息存在时,修改邮箱(无论是否同步修改密码),首次点击更新操作无效,需手动点击两次“更新”按钮才能完成信息更新
尝试过的无效方案
曾尝试定时重复执行删除和保存函数,但未解决问题:
delete(email: result.0) save(email: email, password: password) Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { _ in delete(email: result.0) save(email: email, password: password) }
现有代码
Keychain操作代码
func save(email: String, password: String) { let passwordData = password.data(using: .utf8)! let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "https://hustle.page", kSecAttrAccount as String: email, kSecValueData as String: passwordData ] let saveStatus = SecItemAdd(query as CFDictionary, nil) if saveStatus == errSecDuplicateItem { update(email: email, password: password) } } func update(email: String, password: String) { if let result = read(service: "https://hustle.page"){ if result.0 == email { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "https://hustle.page", kSecAttrAccount as String: email ] let passwordData = password.data(using: .utf8)! let updatedData: [String: Any] = [ kSecValueData as String: passwordData ] SecItemUpdate(query as CFDictionary, updatedData as CFDictionary) } else { delete(email: result.0) save(email: email, password: password) Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { _ in delete(email: result.0) save(email: email, password: password) } } } } func delete(email: String) { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "https://hustle.page", kSecAttrAccount as String: email ] SecItemDelete(query as CFDictionary) } func read(service: String) -> (String, String)? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecReturnAttributes as String: true, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne ] var result: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &result) if status == errSecSuccess, let item = result as? [String: Any] { if let account = item[kSecAttrAccount as String] as? String, let passwordData = item[kSecValueData as String] as? Data, let password = String(data: passwordData, encoding: .utf8) { return (account, password) } } return nil }
视图层代码
Button { save(email: email, password: password) } label: { Text("Update") }
问题根源分析
- 多条目共存导致读取错误:修改邮箱时,原
save函数会直接添加新邮箱的条目,但旧邮箱的条目并未被删除,Keychain中存在同服务下的多条登录信息。 read函数逻辑限制:read使用kSecMatchLimitOne只返回第一条匹配的条目,大概率是旧条目,导致用户看到的信息未更新。update函数触发条件不足:只有当新邮箱条目已存在时,save才会触发update,首次修改邮箱时不会进入update的旧条目删除逻辑。
解决方案
1. 重构save函数,确保单条目存储
修改save函数,每次保存前先删除当前服务下的所有旧条目,保证Keychain中始终只有一条登录信息:
func save(email: String, password: String) { // 先删除当前服务下的所有旧登录条目 let deleteQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "https://hustle.page" ] SecItemDelete(deleteQuery as CFDictionary) // 保存新的登录信息 guard let passwordData = password.data(using: .utf8) else { return } let saveQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "https://hustle.page", kSecAttrAccount as String: email, kSecValueData as String: passwordData ] SecItemAdd(saveQuery as CFDictionary, nil) }
2. 简化update函数逻辑
由于save已经保证单条目存储,update只需处理密码更新场景,若条目不存在则直接调用save:
func update(email: String, password: String) { guard let passwordData = password.data(using: .utf8) else { return } let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: "https://hustle.page", kSecAttrAccount as String: email ] let updateData: [String: Any] = [ kSecValueData as String: passwordData ] let status = SecItemUpdate(query as CFDictionary, updateData as CFDictionary) // 若条目不存在,直接保存新信息 if status == errSecItemNotFound { save(email: email, password: password) } }
3. 视图层代码无需修改
保持原有按钮点击逻辑即可,修改后的save函数会自动处理所有新增、更新场景。
内容的提问来源于stack exchange,提问作者Ahmed Zaidan
相关产品推荐
相关产品推荐

