无法让 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 符合 NSObject
和 NSCoding
并实施了必要的方法。
编辑:感谢大家如此迅速的反馈。代码如下,正如我之前所说,如果我忽略了 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。当使用 NSKeyedArchiver
和 NSCoding
存储枚举时,您需要存储 .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 一起使用。首先,声明您遵守 NSObject
和 NSCoding
协议。然后实现 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了。
在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 符合 NSObject
和 NSCoding
并实施了必要的方法。
编辑:感谢大家如此迅速的反馈。代码如下,正如我之前所说,如果我忽略了 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。当使用 NSKeyedArchiver
和 NSCoding
存储枚举时,您需要存储 .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 一起使用。首先,声明您遵守 NSObject
和 NSCoding
协议。然后实现 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了。