加载数据后不符合 Equatable 的 Swift 对象

Posted

技术标签:

【中文标题】加载数据后不符合 Equatable 的 Swift 对象【英文标题】:Swift Objects not conforming to Equatable after loading data 【发布时间】:2019-11-23 16:21:12 【问题描述】:

从 plist 加载后,我遇到的对象似乎不符合 Equatable 协议的问题。一些背景知识:我有一个对象 TransmitterList 类型,它包含一个 Transmitters 类型的数组。在第一次启动时,一切正常,我可以通过调用删除项目

if let index = transmitterList.transmitters.firstIndex(of: transmitter) 
    transmitterList.transmitters.remove(at: index)

此方法在第一次运行时完美运行,问题出现在应用程序数据已保存并在终止应用程序后重新加载后。所有保存和加载都是使用Codable 协议和 Plist 编码器和解码器完成的。发射器列表中的发射器仍然存在,但在尝试删除发射器时,对firstIndex(of:) 的调用返回nil。其他依赖Equatable 协议的方法也无法正常运行。似乎已保存的发射器不再符合Equatable。但是,在重新启动后添加新发射器时,我能够成功删除任何新添加的发射器。

我能够通过比较一些发射器属性并以这种方式删除它们来创建一个解决方法,但它似乎不是很强大。真的想了解发射机不再符合Equatable 的根本原因。

感谢您的帮助,谢谢!

更新了保存和加载代码。

这是我正在使用的 dataModel 对象,它只是保存了一个 TransmitterLists 数组。 Transmitter 类中没有自定义编码或解码,它只是符合Codable,没有任何额外的方法。

class DataModel 

    // MARK: - Properties
    var transmitterLists = [TransmitterList]()

    // MARK: - Initialization
    init() 
        loadData()
    

    // MARK: - Data Persistence

     func documentsDirectory() -> URL 

         let paths = FileManager.default.urls(for: .documentDirectory,
                                              in: .userDomainMask)
         return paths[0]
     

     func dataFilePath() -> URL 

         return documentsDirectory().appendingPathComponent("FrequencyCoordinator.plist")

     

     func saveData() 

         let encoder = PropertyListEncoder()

         do 

             let data = try encoder.encode(transmitterLists)

             try data.write(to: dataFilePath(), options: Data.WritingOptions.atomic)

             print("DATA SAVED!!")
          catch 
             print("That did not work: \(error.localizedDescription)")
         

     

     func loadData() 

         let path = dataFilePath()

         if let data = try? Data(contentsOf: path) 

             let decoder = PropertyListDecoder()

             do 
                print("DATA LOADED")


                 transmitterLists = try decoder.decode([TransmitterList].self, from: data)


              catch 
                 print("That didnt work ;-< \(error.localizedDescription)")
             
         

     



在 Transmitter 类中添加。任何其他类型也符合Codable,但只能在定义中声明。也许在确保类型正确符合方面还有更多工作要做?

class Transmitter: Equatable, Codable 

    static func == (lhs: Transmitter, rhs: Transmitter) -> Bool 
        return lhs.id == rhs.id
    


    var id: Int 
    var name: String
    var transmitterType: TransmitterType
    var block: Block
    var frequency: Frequency
    var doesOverlap: Bool = false
    var transmitterOverlapRange: ClosedRange<Double>
    var imProductOverlapRanges = [ClosedRange<Double>]()
    var overlapsWith = [Transmitter]()
    var doesHaveTwoTransmitterIMInterference = false
    var doesHaveThreeTransmitterIMInterference = false
    var twoTransmitterIMProductsOverlap = [TwoTransmitterIMProduct]()
    var threeTransmitterIMProductsOverlap = [ThreeTransmitterIMProduct]()
    var isDisabled = false

    var frequencyIsSet: Bool 
        get 
            self.frequency.literal != 0.0
        
    

    init(name: String, type: TransmitterType, block: Block, frequency: Frequency, transmitterOverlapRange: ClosedRange<Double>) 
        self.id = DataModel.nextTransmitterID()
        self.name = name
        self.transmitterType = type
        self.block = block
        self.frequency = frequency
        self.transmitterOverlapRange = transmitterOverlapRange

    

    enum TransmitterType: Int, Codable 
        case talent
        case hops
        case ifb
    


【问题讨论】:

“真的很想了解发射机不再符合 Equatable 的根本原因。”它们确实符合 Equatable。但是根据您对此对象类型的 Equatable 定义,它们不再相等 我想你是对的。虽然我不完全确定在保存数据和重新加载数据之间发生了什么变化。这个问题也对应用程序的其他部分造成了严重破坏。已保存和加载的发射机似乎不再能够更改其频率。所有标签都会根据新频率进行相应更新,但打印声明在幕后显示发射器的频率实际上并未改变。这些问题只有在保存和加载后才会出现。 您是否对发射器对象使用自定义方法.init(decoder:)encode(encoder:)?如果是这样,您可以发布它们,因为我怀疑您的解码方式与编码方式不同。 @flanker 使用保存和加载方法更新了原始问题。 相关的不是加载/保存方法,而是 TransmitterList(和任何子类型)的编码/解码方式。 IE。如何使它们符合 Codable。您可以将 Transmitter 类添加到问题中吗? 【参考方案1】:

您仍然没有显示相关代码。你在抱怨

if let index = transmitterList.transmitters.firstIndex(of: transmitter)

失败,但您没有显示该行在代码中的位置,也没有告诉我们transmitter 是什么或transmitterList 中的内容。

但是,您的表现足以让您猜测一下。您重新定义了 Transmitter 的相等性,使得两个 Transmitter 当且仅当它们的 id 值相等时才相等。

static func == (lhs: Transmitter, rhs: Transmitter) -> Bool 
    return lhs.id == rhs.id

因此我们可以在这一行得出结论:

if let index = transmitterList.transmitters.firstIndex(of: transmitter)

...您的transmitter 与列表中的任何发射器都有不同的id。如果这是一个新的transmitter,这将是有意义的,因为它将使用新的id 生成。

确实,你说:

我能够通过比较一些发射器属性并以这种方式删除它们来创建一个解决方法,但它似乎不是很强大

确实没有。但这表明您似乎期望一个 Transmitter 基于“其某些属性”与另一个 Transmitter 等同。它不是。您的唯一平等点是id。因此,我们可以得出结论,您正在尝试在其他发射器列表中找到具有唯一 idtransmitter — 当然它不存在,因为根据定义,唯一的 id 是唯一的。

【讨论】:

解决您的几点问题。我遇到的主要问题是,只有在保存并重新加载数据后,对 firstIndex(of:) 的调用才会失败。在应用程序第一次运行时,在调用任何保存或加载方法之前,一切正常。我试图获取索引的Transmitter 是我试图从TransmitterList.transmitters 数组中删除的那个。为了解决这个问题,我最初比较了Transmitter.name 属性,以便能够从阵列中移除一个发射器,然后继续为每个Transmitter 提供一个唯一的ID。

以上是关于加载数据后不符合 Equatable 的 Swift 对象的主要内容,如果未能解决你的问题,请参考以下文章

符合 Equatable for Diffing 的协议

Swift,Equatable 协议错误?

RealmObject Equatable 冗余消息

CLLocation 如何实现 Equatable 协议?

RealmObject Equatable冗余消息

Swift 5:在使用协议实现 Equatable 的结构上实现通用数组操作