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

使用NSKeyedUnarchiver解档AppAuth类对象时匹配失败求助

OIDAuthState归档解档后isEqual不匹配的原因及解决方案

核心原因分析

1. 默认isEqual:基于实例指针比较

OIDAuthState如果没有重写isEqual:和hash方法,会沿用NSObject的默认实现——直接对比对象的内存地址指针。你从同一归档Data解档出的是两个完全独立的实例,内存地址不同,所以isEqual:必然返回false,哪怕它们的核心内容完全一致。

2. 内部属性未被完整归档/解码

AppAuth的OIDAuthState实现Secure Coding时,可能存在部分属性未被正确编码,或者解码时会动态生成新的实例对象(比如内部的辅助类、缓存对象)。这些未被归档的属性在解档后会被重新初始化,导致两个解档对象的内部结构存在差异,不仅isEqual:不通过,重新归档时也会因为这些差异生成不同的Data。

3. 包含动态生成的非持久化属性

OIDAuthState内部可能存在一些动态生成的临时属性(比如用于标识实例的UUID、运行时缓存的临时数据),这些属性不会被归档保存,每次解档都会重新生成新值。即使核心认证属性一致,这些动态属性的差异也会导致isEqual:返回false,且重新归档后的Data也会不同。

解决方案

1. 扩展OIDAuthState重写isEqual:和hash

针对核心认证属性(如accessToken、refreshToken、expirationDate、scope等)实现自定义的相等性判断:

extension OIDAuthState {
    override func isEqual(_ object: Any?) -> Bool {
        guard let other = object as? OIDAuthState else { return false }
        return self.accessToken == other.accessToken
            && self.refreshToken == other.refreshToken
            && self.expirationDate == other.expirationDate
            && self.scope == other.scope
            // 补充其他需要对比的核心业务属性
    }
    
    override var hash: Int {
        var hasher = Hasher()
        hasher.combine(accessToken)
        hasher.combine(refreshToken)
        hasher.combine(expirationDate)
        hasher.combine(scope)
        // 对应添加其他核心属性的hash计算
        return hasher.finalize()
    }
}

2. 验证并补全归档逻辑

检查OIDAuthState的encode(with:)和init(coder:)实现(可查看AppAuth源码),确保所有需要参与相等性判断的属性都被正确编码和解码。如果有遗漏的属性,可以通过扩展补充编码逻辑:

extension OIDAuthState {
    override func encode(with coder: NSCoder) {
        super.encode(with: coder)
        // 补充编码自定义需要的属性
        coder.encode(customProperty, forKey: "customProperty")
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        // 补充解码自定义属性
        customProperty = coder.decodeObject(forKey: "customProperty") as? CustomType
    }
}

3. 自定义轻量归档逻辑

如果默认归档包含过多不必要的属性,可以手动序列化核心认证数据,解档时重新构建OIDAuthState对象:

private extension OIDAuthState {
    func archiveToData() throws -> Data {
        let authDict: [String: Any?] = [
            "accessToken": accessToken,
            "refreshToken": refreshToken,
            "expirationDate": expirationDate,
            "scope": scope
            // 其他核心业务属性
        ]
        return try JSONSerialization.data(withJSONObject: authDict)
    }
    
    static func unarchive(from data: Data) throws -> OIDAuthState? {
        guard let authDict = try JSONSerialization.jsonObject(with: data) as? [String: Any?] else { return nil }
        // 手动构建OIDAuthState实例,需根据实际场景调用合适的初始化方法
        let authState = OIDAuthState()
        authState.accessToken = authDict["accessToken"] as? String
        authState.refreshToken = authDict["refreshToken"] as? String
        authState.expirationDate = authDict["expirationDate"] as? Date
        authState.scope = authDict["scope"] as? String
        return authState
    }
}

内容的提问来源于stack exchange,提问作者Guilherme Carvalho

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 23:17:06