转换为 Swift 3 打破了习惯 class encoding/decoding
Converting to Swift 3 broke custom class encoding/decoding
所以我刚刚将一个小应用程序从 Swift 2.2 转换为 Swift 3。我已经摆脱了自动转换器后所需的常见错误和一些清理工作,但我我遇到了一个 运行 时间问题,我无法解决。
我有一个自定义 class,我正在使用 NSCoding 协议保存到 NSUserDefaults。当我尝试从 NSUserDefaults 解码编码的 object 时,它在 guard let duration = decoder.decodeObject(forKey: "duration") as? Int
行失败,因为持续时间打印为 nil。解码标题字符串工作正常,但至少编码函数的那一行工作正常。
这在 2.2 中运行良好,我找不到任何表明 Swift 3 对 NSCoding 进行了更改的信息。任何帮助将不胜感激。
class TimerModel: NSObject, NSCoding, AVAudioPlayerDelegate {
var name: String
var active: Bool
var paused: Bool
var duration: Int
var remainingWhenPaused: Int?
var timerEndTime: Date?
var timerStartTime: Date?
var audioAlert: AlertNoise
var UUID: String
var colorScheme: BaseColor
var alarmRepetitions: Int
var timerRepetitions: Int
var currentTimerRepetition: Int
var audioPlaying: Bool
var player: AVAudioPlayer = AVAudioPlayer()
var countDownTimer: Timer = Timer()
var delegate: timerProtocol? = nil
init(withName name: String, duration: Int, UUID: String, color: BaseColor, alertNoise: AlertNoise, timerRepetitions: Int, alarmRepetitions: Int) {
self.name = name
self.active = false
self.paused = false
self.duration = duration
self.UUID = UUID
self.audioAlert = alertNoise
self.colorScheme = color
self.alarmRepetitions = alarmRepetitions
self.audioPlaying = false
self.timerRepetitions = timerRepetitions
self.currentTimerRepetition = 0
super.init()
}
convenience override init() {
self.init(withName: "Tap Timer 1", duration: 10, UUID: Foundation.UUID().uuidString, color: .Red, alertNoise: .ChurchBell, timerRepetitions: 1, alarmRepetitions: 0)
}
// MARK: NSCoding
required convenience init? (coder decoder: NSCoder) {
print("in init coder:")
print("Name: \(decoder.decodeObject(forKey: "name"))")
print("Duration: \(decoder.decodeObject(forKey: "duration"))")
guard let name = decoder.decodeObject(forKey: "name") as? String
else {
print("init coder name guard failed")
return nil
}
guard let duration = decoder.decodeObject(forKey: "duration") as? Int
else {
print("init coder duration guard failed")
print("duration: \(decoder.decodeObject(forKey: "duration"))")
return nil
}
guard let audioAlertRawValue = decoder.decodeObject(forKey: "audioAlert") as? String
else {
print("init coder audioAlert guard failed")
return nil
}
guard let UUID = decoder.decodeObject(forKey: "UUID") as? String
else {
print("init coder UUID guard failed")
return nil
}
guard let colorSchemeRawValue = decoder.decodeObject(forKey: "colorScheme") as? String
else {
print("init coder colorScheme guard failed")
return nil
}
guard let alarmRepetitions = decoder.decodeObject(forKey: "alarmRepetitions") as? Int
else {
print("init coder alarmRepetitions guard failed")
return nil
}
guard let timerRepetitions = decoder.decodeObject(forKey: "timerRepetitions") as? Int
else {
print("init coder timerRepetitions guard failed")
return nil
}
guard let audioAlert = AlertNoise(rawValue: audioAlertRawValue)
else{
print("No AlertNoise rawValue case found")
return nil
}
guard let colorScheme = BaseColor(rawValue: colorSchemeRawValue)
else{
print("No BaseColor rawValue case found")
return nil
}
print("initCoder guards passed, initing timer")
print("\(name), \(duration), \(UUID), \(colorScheme), \(audioAlert), \(timerRepetitions), \(alarmRepetitions)")
self.init(withName: name, duration: duration, UUID: UUID, color: colorScheme, alertNoise: audioAlert, timerRepetitions: timerRepetitions, alarmRepetitions: alarmRepetitions)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "name")
aCoder.encode(self.duration, forKey: "duration")
aCoder.encode(self.audioAlert.rawValue, forKey: "audioAlert")
aCoder.encode(self.UUID, forKey: "UUID")
aCoder.encode(self.colorScheme.rawValue, forKey: "colorScheme")
aCoder.encode(self.alarmRepetitions, forKey: "alarmRepetitions")
aCoder.encode(self.timerRepetitions, forKey: "timerRepetitions")
}
所以看起来解决方案很简单,但有点不直观。
所以我用通用方法 encode(self.ivar, forKey: "keyName")
对 class ivar 进行了编码,但是如果该 ivar 是一个 int,则需要使用 decodeInteger(forKey: "keyName")
对其进行解码 - 这需要摆脱守卫声明也是因为此方法 return 是非可选的。如果使用通用方法解码,则必须使用整数特定方法解码似乎很奇怪 - Swift 2.2.
中并非如此
SimonBarker 的出色回答,它解决了我遇到的同样问题。我最终将他的解决方案应用到我自己的代码中并对其进行了修改,以便使用通才方法完成编码。您可以 "force" 使用通才方法进行编码,方法是:
func encode(_ objv: Any?, forKey key: String)
所以在你的代码中你可以使用:
aCoder.encode(self.name as Any?, forKey: "name")
这样 self.name 被编码为一个对象并且不会破坏您现有的代码,即:decoder.decodeObject(forKey: "name") as?字符串
这可能不是最优雅的解决方案,但至少它对我有用,无需更改在 Swift 2.3 中运行良好但在 Swift 3 中损坏的代码。 ..
所以我刚刚将一个小应用程序从 Swift 2.2 转换为 Swift 3。我已经摆脱了自动转换器后所需的常见错误和一些清理工作,但我我遇到了一个 运行 时间问题,我无法解决。
我有一个自定义 class,我正在使用 NSCoding 协议保存到 NSUserDefaults。当我尝试从 NSUserDefaults 解码编码的 object 时,它在 guard let duration = decoder.decodeObject(forKey: "duration") as? Int
行失败,因为持续时间打印为 nil。解码标题字符串工作正常,但至少编码函数的那一行工作正常。
这在 2.2 中运行良好,我找不到任何表明 Swift 3 对 NSCoding 进行了更改的信息。任何帮助将不胜感激。
class TimerModel: NSObject, NSCoding, AVAudioPlayerDelegate {
var name: String
var active: Bool
var paused: Bool
var duration: Int
var remainingWhenPaused: Int?
var timerEndTime: Date?
var timerStartTime: Date?
var audioAlert: AlertNoise
var UUID: String
var colorScheme: BaseColor
var alarmRepetitions: Int
var timerRepetitions: Int
var currentTimerRepetition: Int
var audioPlaying: Bool
var player: AVAudioPlayer = AVAudioPlayer()
var countDownTimer: Timer = Timer()
var delegate: timerProtocol? = nil
init(withName name: String, duration: Int, UUID: String, color: BaseColor, alertNoise: AlertNoise, timerRepetitions: Int, alarmRepetitions: Int) {
self.name = name
self.active = false
self.paused = false
self.duration = duration
self.UUID = UUID
self.audioAlert = alertNoise
self.colorScheme = color
self.alarmRepetitions = alarmRepetitions
self.audioPlaying = false
self.timerRepetitions = timerRepetitions
self.currentTimerRepetition = 0
super.init()
}
convenience override init() {
self.init(withName: "Tap Timer 1", duration: 10, UUID: Foundation.UUID().uuidString, color: .Red, alertNoise: .ChurchBell, timerRepetitions: 1, alarmRepetitions: 0)
}
// MARK: NSCoding
required convenience init? (coder decoder: NSCoder) {
print("in init coder:")
print("Name: \(decoder.decodeObject(forKey: "name"))")
print("Duration: \(decoder.decodeObject(forKey: "duration"))")
guard let name = decoder.decodeObject(forKey: "name") as? String
else {
print("init coder name guard failed")
return nil
}
guard let duration = decoder.decodeObject(forKey: "duration") as? Int
else {
print("init coder duration guard failed")
print("duration: \(decoder.decodeObject(forKey: "duration"))")
return nil
}
guard let audioAlertRawValue = decoder.decodeObject(forKey: "audioAlert") as? String
else {
print("init coder audioAlert guard failed")
return nil
}
guard let UUID = decoder.decodeObject(forKey: "UUID") as? String
else {
print("init coder UUID guard failed")
return nil
}
guard let colorSchemeRawValue = decoder.decodeObject(forKey: "colorScheme") as? String
else {
print("init coder colorScheme guard failed")
return nil
}
guard let alarmRepetitions = decoder.decodeObject(forKey: "alarmRepetitions") as? Int
else {
print("init coder alarmRepetitions guard failed")
return nil
}
guard let timerRepetitions = decoder.decodeObject(forKey: "timerRepetitions") as? Int
else {
print("init coder timerRepetitions guard failed")
return nil
}
guard let audioAlert = AlertNoise(rawValue: audioAlertRawValue)
else{
print("No AlertNoise rawValue case found")
return nil
}
guard let colorScheme = BaseColor(rawValue: colorSchemeRawValue)
else{
print("No BaseColor rawValue case found")
return nil
}
print("initCoder guards passed, initing timer")
print("\(name), \(duration), \(UUID), \(colorScheme), \(audioAlert), \(timerRepetitions), \(alarmRepetitions)")
self.init(withName: name, duration: duration, UUID: UUID, color: colorScheme, alertNoise: audioAlert, timerRepetitions: timerRepetitions, alarmRepetitions: alarmRepetitions)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: "name")
aCoder.encode(self.duration, forKey: "duration")
aCoder.encode(self.audioAlert.rawValue, forKey: "audioAlert")
aCoder.encode(self.UUID, forKey: "UUID")
aCoder.encode(self.colorScheme.rawValue, forKey: "colorScheme")
aCoder.encode(self.alarmRepetitions, forKey: "alarmRepetitions")
aCoder.encode(self.timerRepetitions, forKey: "timerRepetitions")
}
所以看起来解决方案很简单,但有点不直观。
所以我用通用方法 encode(self.ivar, forKey: "keyName")
对 class ivar 进行了编码,但是如果该 ivar 是一个 int,则需要使用 decodeInteger(forKey: "keyName")
对其进行解码 - 这需要摆脱守卫声明也是因为此方法 return 是非可选的。如果使用通用方法解码,则必须使用整数特定方法解码似乎很奇怪 - Swift 2.2.
SimonBarker 的出色回答,它解决了我遇到的同样问题。我最终将他的解决方案应用到我自己的代码中并对其进行了修改,以便使用通才方法完成编码。您可以 "force" 使用通才方法进行编码,方法是:
func encode(_ objv: Any?, forKey key: String)
所以在你的代码中你可以使用:
aCoder.encode(self.name as Any?, forKey: "name")
这样 self.name 被编码为一个对象并且不会破坏您现有的代码,即:decoder.decodeObject(forKey: "name") as?字符串
这可能不是最优雅的解决方案,但至少它对我有用,无需更改在 Swift 2.3 中运行良好但在 Swift 3 中损坏的代码。 ..