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

Swift中使用Codable保存泛型结构到UIDocument编码报错

问题:自定义泛型结构体Tree编码保存到UIDocument时抛出unrecognized selector错误

错误信息

运行时抛出如下崩溃日志:

EigenFlow[81334:7048816] -[__SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x60000195aee0

现有实现代码

UIDocument子类实现

class Document: UIDocument {
    
    var saveUniqueTree : Tree<Unique<Int>> = uniqueTree
    var objectsArray = [Any]()
    
    override func contents(forType typeName: String) throws -> Any {
        
        self.objectsArray.removeAll()
        self.objectsArray.append(saveUniqueTree)

        print("ObjectsArray After: \(objectsArray)")
        
        let graphData = try NSKeyedArchiver.archivedData(withRootObject: objectsArray, requiringSecureCoding: false)

        return graphData
    }
}

自定义类型Codable实现

struct Tree<A: Codable>: Codable {
    var value: A
    var children: [Tree<A>] = []
    
    enum CodingKeys: String, CodingKey {
            case value, children
    }

    init(_ value: A, children: [Tree<A>] = []) {
        self.value = value
        self.children = children
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        
        value = try values.decode(A.self, forKey: .value)
        children = try values.decode([Tree<A>].self, forKey: .children)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(value, forKey: .value)
        try container.encode(children, forKey: .children)
    }
}


class Unique<A: Codable>: Identifiable, Codable {
    var value: A
    
    enum CodingKeys: String, CodingKey {
            case value
    }
    
    init(_ value: A) { self.value = value }
    
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        do {
            try container.encode(value, forKey: .value)
        }
        catch {
            fatalError("Should never happen")
        }
    }
}

struct Diagram<A: Codable & Identifiable, V: View>: View {
    let tree: Tree<A>
    let node: (A) -> V
    
    typealias Key = CollectDict<A.ID, Anchor<CGPoint>>

    var body: some View {
        VStack(alignment: .center) {
            node(tree.value)
               .anchorPreference(key: Key.self, value: .center, transform: {
                   [self.tree.value.id: $0]
               })
            HStack(alignment: .bottom, spacing: 10) {
                ForEach(tree.children, id: \.value.id, content: { child in
                    Diagram(tree: child, node: self.node)
                })
            }
        }.backgroundPreferenceValue(Key.self, { (centers: [A.ID: Anchor<CGPoint>]) in
            GeometryReader { proxy in
                ForEach(self.tree.children, id: \.value.id, content: {
                 child in
                    let childValue = tree.value
                    Line(
                        from: proxy[centers[self.tree.value.id]!],
                        to: proxy[centers[child.value.id]!])
                        .stroke(Color.init(grayBarColor), style: StrokeStyle(lineWidth: 7))
                        .onAppear {

                        }
                })
            }
        })
    }
}

var uniqueTree: Tree<Unique<Int>> = binaryTree.map(Unique.init) 

全局变量uniqueTree支持全局任意位置访问,需求是正常将uniqueTree编码保存到UIDocument中,不触发上述崩溃,确认当前实现的遗漏问题。


问题根因

  • 核心错误是混淆了Codable协议和NSCoding协议:
    • NSKeyedArchiver是Objective-C时代遗留的归档工具,只支持遵守NSCoding(或NSSecureCoding)协议的NSObject子类类型,不支持Swift原生的Codable类型。
    • 定义的Tree是Swift结构体、Unique虽然是类但没有继承NSObject也没有实现NSCoding相关方法,直接塞给NSKeyedArchiver时,系统会把Swift值类型桥接成不响应encodeWithCoder:的__SwiftValue类型,直接触发找不到选择器的崩溃。
  • 额外问题:Unique类的Codable实现缺失init(from:)初始化方法,就算用正确的Codable归档方式也会报错。代码中声明的[Any]类型数组也不支持Codable归档,无实际存在必要。

修复方案

方案1:用Swift原生编码器替代NSKeyedArchiver(推荐)

这是Swift类型归档的标准方案,不需要改动现有Codable核心实现,只需要补全Unique类缺失的解码器方法,再修改UIDocument内的归档逻辑即可。

  1. 补全Unique类的init(from:)实现:
required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    value = try container.decode(A.self, forKey: .value)
}
  1. 修改contents(forType:)方法内的归档逻辑,用JSONEncoder替换NSKeyedArchiver,移除无用的objectsArray:
override func contents(forType typeName: String) throws -> Any {
    let encoder = JSONEncoder()
    // 如果需要更小的二进制体积,可替换为PropertyListEncoder
    let graphData = try encoder.encode(saveUniqueTree)
    return graphData
}
  1. 实现对应的读取逻辑load(fromContents:),用JSONDecoder解码:
override func load(fromContents contents: Any, ofType typeName: String?) throws {
    guard let data = contents as? Data else {
        throw NSError(domain: "DocumentError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的文档数据"])
    }
    let decoder = JSONDecoder()
    saveUniqueTree = try decoder.decode(Tree<Unique<Int>>.self, from: data)
}

方案2:如果必须使用NSKeyedArchiver

需要让所有要归档的类型继承NSObject并遵守NSSecureCoding协议,手动实现encode(with coder:)和init?(coder:)方法,这种方式对Swift泛型结构体适配成本极高,容易出现兼容性问题,不推荐使用。


内容的提问来源于stack exchange,提问作者Paul Curtis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 01:36:19