使用NSKeyedUnarchiver解档AppAuth类对象时匹配失败求助
核心原因分析
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

