【问题标题】:Unable to deserialise a nested class with NSCoding无法使用 NSCoding 反序列化嵌套类
【发布时间】:2017-01-20 06:03:00
【问题描述】:

我有一个复杂的类,其中包含许多嵌套类到几个层次,这些类被序列化并存储在 CoreData 表中。问题是,由于无法反序列化升级到 Swift 3 类的实例,因此在尝试解码嵌套类的实例时失败。

为了说明问题,使用实现 NSCoding 的内部类创建一个类定义:

导入基础

class Foo : NSObject, NSCoding {

    class Bar : NSObject, NSCoding {
        var x : Int

        init(x:Int) {
            self.x = x
        }

        required init?(coder aDecoder: NSCoder) {
            self.x = aDecoder.decodeInteger(forKey: "x")
        }

        func encode(with aCoder: NSCoder) {
            aCoder.encode(x, forKey:"x")
        }
    }

    var bar:Bar

    init(x:Int) {
        self.bar = Bar(x: x)
    }

    required init?(coder aDecoder: NSCoder) {
        self.bar = aDecoder.decodeObject(forKey: "bar") as! Bar
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(bar, forKey:"bar")
    }
}

然后构建单元测试:

import XCTest
@testable import SerializationDemo

class SerializationDemoTests: XCTestCase {

     func fileURL() -> URL {

        let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        return dir.appendingPathComponent("foo.dat")

    }

    func testSerialize() {
        let foo = Foo(x:42)
        let data = NSKeyedArchiver.archivedData(withRootObject: foo)

        let url = fileURL()

        do {
            try data.write(to: url)
        } catch {
            XCTFail("\(error)")
        }
    }

    func testDeserialise() {
        let url = fileURL()
        do {
            let data = try Data(contentsOf: url)
            let myfoo = NSKeyedUnarchiver.unarchiveObject(with: data) as! Foo
            XCTAssertEqual(myfoo.bar.x, 42)
        } catch {
            XCTFail("\(error)")
        }

    }
}

现在通过选择测试用例本身在一个会话中运行两个测试。这将按预期正确序列化和反序列化对象。

现在尝试独立运行两个单元测试。先连载。反序列化。第二个测试失败了

失败:捕获“NSInvalidUnarchiveOperationException”,“*** -[NSKeyedUnarchiver decodeObjectForKey:]: 无法为键 (bar) 解码类 (_TtCC17SerializationDemo3Foo3Bar) 的对象;班级可能 在源代码或未链接的库中定义”

一种解决方案是重构代码,使所有内部类都成为顶级,这还需要重命名这些类以使它们在应用程序中全局唯一。我宁愿不必以这种方式返工代码

【问题讨论】:

    标签: swift swift3


    【解决方案1】:

    我确定这是 Swift 3 中的一个错误,可能是为了优化类加载器。在通过其他方式加载类定义之前,类加载器似乎不会在反序列化期间直接加载内部类。

    在示例测试中添加一行

    _ = Foo(x: 0)
    

    testDeserialise() 解决了这个问题。显然是通过预加载必要的类定义。

    我能够通过向复杂类添加一些代码以在第一次尝试反序列化实例时递归地预加载每个内部类的实例来解决我的应用程序中的问题:

    基本上:

    if !preloaded {
       for x in  allElements() {
           _ = x.allOperations()
       }
       preloaded = true
    }
    

    其中 allElements() 返回实例化每个内部类的一个实例的列表,而 allOperations() 返回每个内部类的所有内部类。

    更新

    我已向 Apple #30034842 提交了错误报告

    Apple 工程部门还提出了另一种临时解决方法。当解码对象知道可以解码的可能类时使用NSCoder.decodeObject(of: [AnyClass]?, forKey: String)

    他们还注意到:

    您还应该为 Bar 分配一个 @objc 名称(例如 @objc(Bar));我们不 随着时间的推移,保证 Swift 错位名称的稳定性——对于 向后和向前兼容性,你应该给它一个规范的 名字。

    更新 2018 年 3 月

    这在 Xcode 9 中已修复,即使使用 Swift 语言版本 3.2 进行编译也是如此。此外,在编码期间在运行时强制分配 @objc 名称。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-08-11
      • 1970-01-01
      • 2018-08-01
      • 2013-10-12
      • 1970-01-01
      • 2012-11-18
      • 2020-05-23
      相关资源
      最近更新 更多