简体   繁体   中英

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.

I've got a custom class that I am saving to NSUserDefaults with the NSCoding protocol. When I try to decode the encoded object from NSUserDefaults it fails on the 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. 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. 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. 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.

Excellent answer by SimonBarker and it solved the same issue I had. 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? 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...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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