能否将模型对象数组存入Keychain,如何正确存取并按categoryName筛选?
实现方案
第一步:改造UserData支持序列化
因为Keychain只能存储Data类型数据,首先需要让模型遵守Codable协议,单独处理不支持默认编解码的UIImage类型:
import UIKit struct UserData: Codable { var userName: String? = nil var password: String? = nil var phone: String? = nil var email: String? = nil var image: UIImage? = nil var categoryName: String? = nil enum CodingKeys: String, CodingKey { case userName, password, phone, email, image, categoryName } // 自定义解码逻辑,把Data转回UIImage init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) userName = try container.decodeIfPresent(String.self, forKey: .userName) password = try container.decodeIfPresent(String.self, forKey: .password) phone = try container.decodeIfPresent(String.self, forKey: .phone) email = try container.decodeIfPresent(String.self, forKey: .email) categoryName = try container.decodeIfPresent(String.self, forKey: .categoryName) if let imageData = try container.decodeIfPresent(Data.self, forKey: .image) { image = UIImage(data: imageData) } else { image = nil } } // 自定义编码逻辑,把UIImage转成PNG Data func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(userName, forKey: .userName) try container.encodeIfPresent(password, forKey: .password) try container.encodeIfPresent(phone, forKey: .phone) try container.encodeIfPresent(email, forKey: .email) try container.encodeIfPresent(categoryName, forKey: .categoryName) if let image = image { let imageData = image.pngData() try container.encodeIfPresent(imageData, forKey: .image) } } }
第二步:封装Keychain原生存取工具
基于系统自带的Security框架实现存取逻辑,不需要引入第三方依赖:
import Security class KeychainManager { static let shared = KeychainManager() // 替换为你App专属的唯一标识,避免和其他Keychain存储冲突 private let serviceID = "com.yourApp.business.userData" // 存储UserData数组到Keychain @discardableResult func saveUserDataList(_ list: [UserData]) -> Bool { do { // 数组编码为Data let data = try JSONEncoder().encode(list) // 先删除旧数据避免重复存储 deleteUserDataList() let query: [CFString: Any] = [ kSecClass: kSecClassGenericPassword, kSecAttrService: serviceID, kSecValueData: data, kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked // 可根据业务需求调整访问权限 ] let status = SecItemAdd(query as CFDictionary, nil) return status == errSecSuccess } catch { print("模型编码失败:\(error)") return false } } // 读取全部UserData数组 func fetchAllUserDataList() -> [UserData]? { let query: [CFString: Any] = [ kSecClass: kSecClassGenericPassword, kSecAttrService: serviceID, kSecReturnData: kCFBooleanTrue!, kSecMatchLimit: kSecMatchLimitOne ] var dataRef: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &dataRef) guard status == errSecSuccess, let data = dataRef as? Data else { return nil } do { return try JSONDecoder().decode([UserData].self, from: data) } catch { print("模型解码失败:\(error)") return nil } } // 按categoryName筛选读取 func fetchUserData(for category: String) -> [UserData] { guard let allList = fetchAllUserDataList() else { return [] } // 直接对字段做过滤即可 return allList.filter { $0.categoryName == category } } // 删除已存储的UserData数组 @discardableResult func deleteUserDataList() -> Bool { let query: [CFString: Any] = [ kSecClass: kSecClassGenericPassword, kSecAttrService: serviceID ] let status = SecItemDelete(query as CFDictionary) return status == errSecSuccess } }
第三步:使用示例
// 存储数据 let user1 = UserData(userName: "张三", password: "123456", categoryName: "普通用户") let user2 = UserData(userName: "李四", password: "abc123", categoryName: "管理员") KeychainManager.shared.saveUserDataList([user1, user2]) // 读取全量数据 let allUsers = KeychainManager.shared.fetchAllUserDataList() // 按分类筛选数据 let adminUsers = KeychainManager.shared.fetchUserData(for: "管理员")
注意事项
- Keychain适合存储小体量敏感数据,如果你的数组元素多、图片体积大,建议把图片存在沙盒,Keychain只存储其他业务字段加图片路径,性能会更好
- 存取数据如果遇到大数据量,建议放到子线程执行,避免阻塞主线程
内容的提问来源于stack exchange,提问作者islahulislam.ca
相关产品推荐
相关产品推荐

