You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否将模型对象数组存入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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 22:45:01