简体   繁体   English

转换为Swift 3打破了自定义类编码/解码

[英]Converting to Swift 3 broke custom class encoding/decoding

So I've just converted a small app from Swift 2.2 to Swift 3. I've gotten rid of the usual errors and bits of mop up required after the auto converter but I've got a run time issue that I can't work out. 所以我刚刚将一个小应用程序从Swift 2.2转换为Swift 3.我已经摆脱了自动转换器后所需的通常错误和拖把,但我有一个运行时问题我无法工作出。

I've got a custom class that I am saving to NSUserDefaults with the NSCoding protocol. 我有一个自定义类,我使用NSCoding协议保存到NSUserDefaults。 When I try to decode the encoded object from NSUserDefaults it fails on the guard let duration = decoder.decodeObject(forKey: "duration") as? Int 当我尝试从NSUserDefaults解码编码对象时,它在guard let duration = decoder.decodeObject(forKey: "duration") as? Int上失败, guard let duration = decoder.decodeObject(forKey: "duration") as? Int guard let duration = decoder.decodeObject(forKey: "duration") as? Int line as duration is printing as nil. guard let duration = decoder.decodeObject(forKey: "duration") as? Int行作为持续时间打印为零。 Decoding the title string works fine however so at least that line of the encode function is working correctly. 解码标题字符串工作正常,但至少编码函数的行正常工作。

This worked fine in 2.2 and I can't find anything indicating that Swift 3 made changes to the NSCoding. 这在2.2中运行良好,我找不到任何迹象表明Swift 3对NSCoding进行了更改。 Any help would be much appreciated. 任何帮助将非常感激。

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")
    }

So it seems the solution is simple if a little unintuitive. 因此,如果有点不直观,似乎解决方案很简单。

So I encoded the class ivars with the general method encode(self.ivar, forKey: "keyName") however if that ivar is an int it needs to be decoded with decodeInteger(forKey: "keyName") - this entails getting rid of the guard statements as well since this method return an non-optional. 所以我用常规方法encode(self.ivar, forKey: "keyName")类ivars encode(self.ivar, forKey: "keyName")但是如果那个ivar是一个int,它需要用decodeInteger(forKey: "keyName")解码decodeInteger(forKey: "keyName") - 这需要摆脱它保护语句也是因为这个方法返回一个非可选的。 Seems odd to have to decode with the integer specific method if it was decoded with the generalist method - this wasn't the case in Swift 2.2. 如果使用通才方法解码,则看起来很奇怪必须使用整数特定方法进行解码 - 在Swift 2.2中并非如此。

Excellent answer by SimonBarker and it solved the same issue I had. SimonBarker给出了很好的答案,它解决了我遇到的同样问题。 I eventually applied his solution to my own code and revised it too so that encoding is done with the generalist method. 我最终将他的解决方案应用于我自己的代码并对其进行了修改,以便使用通用方法完成编码。 You can "force" encoding with the generalist method by using: 您可以使用通用方法“强制”编码:

func encode(_ objv: Any?, forKey key: String)

So in your code you could use: 所以在你的代码中你可以使用:

aCoder.encode(self.name as Any?, forKey: "name")

That way self.name is encoded as an object and does not break your existing code, namely: decoder.decodeObject(forKey: "name") as? 这样self.name被编码为一个对象,并不会破坏你现有的代码,即:decoder.decodeObject(forKey:“name”)为? String

This might not be the most elegant solution, but at least it worked for me without the need to change code that worked beautifully in Swift 2.3, but which was broken in Swift 3... 这可能不是最优雅的解决方案,但至少它对我有用而无需更改在Swift 2.3中运行良好的代码,但在Swift 3中被破坏了......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM