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

Posted

技术标签:

【中文标题】转换为 Swift 3 破坏了自定义类编码/解码【英文标题】:Converting to Swift 3 broke custom class encoding/decoding 【发布时间】:2016-09-15 11:30:11 【问题描述】:

所以我刚刚将一个小型应用程序从 Swift 2.2 转换为 Swift 3。我已经摆脱了自动转换器后所需的常见错误和一些拖把,但我有一个运行时问题,我可以没办法。

我有一个使用 NSCoding 协议保存到 NSUserDefaults 的自定义类。当我尝试从 NSUserDefaults 解码编码对象时,它在 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")
    

【问题讨论】:

init(withName... 不符合命名约定 init(name...。与 Objective-C 交互时可能会遇到问题。 谢谢@vadian 我只是在复习命名约定 【参考方案1】:

所以看起来解决方案很简单,如果有点不直观。

所以我使用通用方法encode(self.ivar, forKey: "keyName") 对类 ivars 进行了编码,但是如果该 ivar 是 int,则需要使用 decodeInteger(forKey: "keyName") 对其进行解码 - 这也需要摆脱保护语句,因为此方法返回一个非选修的。如果使用通用方法解码,则必须使用整数特定方法进行解码似乎很奇怪 - 在 Swift 2.2 中并非如此。

【讨论】:

“如果用通用方法解码,必须用整数特定方法解码似乎很奇怪”这让我很头疼。 同意@George,感觉像是一个错误,但老实说,我不打算提交报告,因为 Apple 可能不会解决它 根据我的观察 decodeObject() 解码是否适合 Int?但如果你有 Int,则必须更改为 decodeInteger()。现在,我想知道所有其他非可选类型。【参考方案2】:

SimonBarker 的出色回答解决了我遇到的同样问题。我最终将他的解决方案应用于我自己的代码并对其进行了修改,以便使用通用方法完成编码。您可以使用通用方法“强制”编码:

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 3 破坏了自定义类编码/解码的主要内容,如果未能解决你的问题,请参考以下文章

使用 Qt 解码使用 Java ImageIO 类编码的图像

字符串工具类数组工具类集合工具类转型操作工具类编码与解码操作工具类

我想将字符串值从 API 转换为自定义可编码模型 SWIFT

Apollo graphQL:在 iOS swift 中使用自定义标量时解码数组时出错

向 info.plist 添加了自定义键,当我从 Swift 的游乐场访问时,它是 nil

Swift 3.1:自定义错误转换为 NSError 以访问其域属性时崩溃