无法让 NSKeyedArchiver 工作:无法识别的选择器

Can't get NSKeyedArchiver to work: unrecognized selector

UITableView删除行并保存数据源时我也想保存到持久存储。

我正在使用 NSKeyedArchiver 来保存我的自定义对象类型 Participant 的数组,但它立即失败了:

2018-02-13 23:01:41.818945+0000 ProjectName[28680:5720571] -[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x60800004d290

2018-02-13 23:01:41.826553+0000 ProjectName[28680:5720571] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x60800004d290'

我到处找,找不到适合我的。据我所知,Participant 对象的数据 class 符合 NSObjectNSCoding 并实施了必要的方法。

编辑:感谢大家如此迅速的反馈。代码如下,正如我之前所说,如果我忽略了 post 一些可能与我的经验相关的必要内容,那么非常感谢您的帮助!

数据Class(节选)

class Participant: NSObject, NSCoding {

    //MARK: Properties
    var name: String
    var jobTitle: String?
    var picture: UIImage?
    var rate: Float
    var ratePeriod: ratePeriods

   //MARK: Archiving Paths

    static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
    static let ArchiveURL = DocumentsDirectory.appendingPathComponent("participants")

    //MARK: Types

    struct PropertyKey {
        //THESE MUST NOT CHANGE ONCE BUILT
        static let name = "name"
        static let jobTitle = "jobTitle"
        static let picture = "picture"
        static let rate = "rate"
        static let ratePeriod = "ratePeriod"
    }

    //MARK: Initialisation

    init?(name: String, jobTitle: String?, picture: UIImage?, rate: Float?, ratePeriod: ratePeriods?) {

        //The name must not be empty
        guard !name.isEmpty else {
            return nil
        }

        //Init stored properties
        self.name = name
        self.jobTitle = jobTitle ?? ""
        self.picture = picture
        self.rate = rate ?? 0.0
        self.ratePeriod = ratePeriod ?? .annually
    }

    //MARK: NSCoding
    func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: PropertyKey.name)
        aCoder.encode(jobTitle, forKey: PropertyKey.jobTitle)
        aCoder.encode(picture, forKey: PropertyKey.picture)
        aCoder.encode(rate, forKey: PropertyKey.rate)
        aCoder.encode(ratePeriod, forKey: PropertyKey.ratePeriod)
    }

    required convenience init?(coder aDecoder: NSCoder) {
        //The name is required. If we cannot decode a name string, the init should fail.
        guard let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String else {
            return nil
        }

        let jobTitle = aDecoder.decodeObject(forKey: PropertyKey.jobTitle) as? String
        let picture = aDecoder.decodeObject(forKey: PropertyKey.picture) as? UIImage
        let rate = aDecoder.decodeFloat(forKey: PropertyKey.rate)
        let ratePeriod = aDecoder.decodeObject(forKey: PropertyKey.ratePeriod) as? ratePeriods


        //Must call designated init
        self.init(name: name, jobTitle: jobTitle, picture: picture, rate: rate, ratePeriod: ratePeriod)
    }

}

从 Private Func 内部我试图使用它但它坏了

   let archivePath = Participant.ArchiveURL.path
   let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(participants, toFile: archivePath)

再次感谢您提供的任何帮助。

编辑 2:我做了更多的调试,我想我对 Xcode IDE 的经验不足,调试阻碍了这一点,但它看起来是正在存储的对象的 ratePeriod 属性 抛出错误。这个 属性 是一个结构,我认为其他人认为这是一个问题。这是可以解决的,还是我需要寻找一种不同的方法来持久存储 Structs?

编辑 3:我已经解决了问题,但不知道如何将其标记为已解决。问题不是结构(我没有存储)而是 Enum。当使用 NSKeyedArchiverNSCoding 存储枚举时,您需要存储 .rawValue 并将其重构为 Int。即:-

编码

aCoder.encode(ratePeriod.rawValue, forKey: PropertyKey.ratePeriod)

解码

let ratePeriodIntValue = (aDecoder.decodeObject(forKey: PropertyKey.ratePeriod) as? Int) ?? ratePeriods.annually.rawValue
let ratePeriod = ratePeriods(rawValue: ratePeriodIntValue)

尝试将 @objc 添加到 swift 等效的 encodeWithCoder: 方法中。 decode init 方法同上。我怀疑这是解决方案,但可能是。

此外,请说明您是如何编写 Swift 中的 NSCoding 方法的。也许它们不匹配,考虑到 Swift 和 ObjC 之间的固有转换如何编写它们?

func encode(with aCoder: NSCoder) {

关于如何使您的自定义 classes NSCoding/NSKeyedArchiver 兼容有一个非常 simple article on NSHipster 的内容。我建议看一看。

作为一个基本答案,为了使您的对象与 NSKeyedArchiver 一起工作,您需要告诉 coder/decoder 如何 encode/decode 您的对象。例如,如果这是您的 class:

class Participant {

    let name: String
    let id: String
    let age: Int

    init(name: String, id: String, age: Int) {
        self.name = name
        self.id = id
        self.age = age
    }

}

您需要进行以下修改才能使其与 NSKeyedArchiver 一起使用。首先,声明您遵守 NSObjectNSCoding 协议。然后实现 encode 方法和 convenience init?(coder:_) 初始化器:

class Participant: NSObject, NSCoding {

    let name: String
    let id: String
    let age: Int

    init(name: String, id: String, age: Int) {
        self.name = name
        self.id = id
        self.age = age
    }

    required convenience init?(coder aDecoder: NSCoder) {
        guard let name = aDecoder.decodeObject(forKey: "name") as? String,
            let id = aDecoder.decodeObject(forKey: "id") as? String,
            let age = aDecoder.decodeObject(forKey: "age") as? Int else { return nil }

        self.init(name: name, id: id, age: age)
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(self.name, forKey: "name")
        aCoder.encode(self.id, forKey: "id")
        aCoder.encode(self.age, forKey: "age")
    }

}

您的自定义对象类型 Participant 需要是 class(不是结构),特别是它必须是 NSObject 的子class。现在你可以成功采用NSCoding了。