【问题标题】:Save array of different object types in UserDefaults在 UserDefaults 中保存不同对象类型的数组
【发布时间】:2021-09-23 07:57:06
【问题描述】:

我有两个类,Radio 和 Podcast,Media 类的孩子。 我正在尝试将一系列媒体(带有收音机和播客)保存到 UserDefaults,但是当我取回它时,我只有媒体(我正在丢失收音机或播客的信息)。 我无法将这些项目投射到 Radio 或 Podcast。

    private func saveRecentMediaInData(_ medias:[Media]) {
        let encoder = JSONEncoder()
        if let encoded = try? encoder.encode(medias) {
            UserDefaults.standard.setValue(encoded, forKey: recentMediasKey)
        }
    }
    
    private func getRecentMediasFromData() -> [Media] {
        let defaults = UserDefaults.standard
        if let data = defaults.value(forKey: recentMediasKey) as? Data {
            let decoder = JSONDecoder()
            if let decoded = try? decoder.decode(Array.self, from: data) as [Media] {
                return decoded
            }
        }
        return []
    }

谢谢

【问题讨论】:

    标签: swift parent-child swiftyuserdefaults


    【解决方案1】:

    该问题与UserDefaults 无关。它有一个混合对象数组,要使用Codable 解码。

    在这种情况下,解决方案是使用带有关联值的enum

    enum Mixed: Codable {
    
        case radio(Radio)
        case podcast(Podcast)
    
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            if let asRadio = try? container.decode(Radio.self) {
                self = .radio(asRadio)
            } else if let asPodcast = try? container.decode(Podcast.self) {
                self = .podcast(asPodcast)
            } else {
                fatalError("Oops")
            }
        }
    }
    

    这是一个完整的示例代码:

    struct SubClassesCodable {
        class Media: Codable, CustomStringConvertible {
            var title: String
    
            var description: String {
                return "Media: \(title)"
            }
        }
        class Radio: Media {
            var channel: Int
            enum CodingKeys: String, CodingKey {
                case channel
            }
    
            required init(from decoder: Decoder) throws {
                let container = try decoder.container(keyedBy: CodingKeys.self)
                self.channel = try container.decode(Int.self, forKey: .channel)
                try super.init(from: decoder)
            }
    
            override func encode(to encoder: Encoder) throws {
                try super.encode(to: encoder)
                var container = encoder.container(keyedBy: CodingKeys.self)
                try container.encode(channel, forKey: .channel)
            }
    
            override var description: String {
                return "Radio: \(title) - \(channel)"
            }
        }
        class Podcast: Media {
            var author: String
            enum CodingKeys: String, CodingKey {
                case author
            }
    
            required init(from decoder: Decoder) throws {
                let container = try decoder.container(keyedBy: CodingKeys.self)
                self.author = try container.decode(String.self, forKey: .author)
                try super.init(from: decoder)
            }
    
            override func encode(to encoder: Encoder) throws {
                try super.encode(to: encoder)
                var container = encoder.container(keyedBy: CodingKeys.self)
                try container.encode(author, forKey: .author)
            }
    
            override var description: String {
                return "Podcast: \(title) - \(author)"
            }
        }
    
        enum Mixed: Codable {
    
            case radio(Radio)
            case podcast(Podcast)
    
            init(from decoder: Decoder) throws {
                let container = try decoder.singleValueContainer()
                if let asRadio = try? container.decode(Radio.self) {
                    self = .radio(asRadio)
                } else if let asPodcast = try? container.decode(Podcast.self) {
                    self = .podcast(asPodcast)
                } else {
                    fatalError("Oops") //Or rather throws a custom error
                }
            }
        }
    
        static func test() {
            let mediaJSONStr = #"{"title": "media"}"#
            let radioJSONStr = #"{"title": "radio", "channel": 3}"#
            let podcastJSONStr = #"{"title": "podcast", "author": "myself"}"#
    
            do {
                let decoder = JSONDecoder()
                //Create values from JSON
                let media = try decoder.decode(Media.self, from: Data(mediaJSONStr.utf8))
                print(media)
                let radio = try decoder.decode(Radio.self, from: Data(radioJSONStr.utf8))
                print(radio)
                let podcast = try decoder.decode(Podcast.self, from: Data(podcastJSONStr.utf8))
                print(podcast)
                let array: [Media] = [radio, podcast]
                print(array)
    
                // Encode to Data, that's what's saved into UserDefaults
                let encoder = JSONEncoder()
                let encodedArray = try encoder.encode(array)
                print("Encoded: \(String(data: encodedArray, encoding: .utf8)!)") //It's more readable as JSON String than Data
    
                //This will fail, it's the current author code
                let decoded = try decoder.decode([Media].self, from: encodedArray)
                print(decoded)
                decoded.forEach {
                    if let asRadio = $0 as? Radio {
                        print(asRadio)
                    }else if let asPodcast = $0 as? Podcast {
                        print(asPodcast)
                    } else {
                        print("Nop: \($0)")
                    }
                }
    
                //This is a working solution
                let mixedDecoded = try decoder.decode([Mixed].self, from: encodedArray)
                let decodedArray: [Media] = mixedDecoded.map {
                    switch $0 {
                    case .radio(let radio):
                        return radio
                    case .podcast(let podcast):
                        return podcast
                    }
                }
                print(decodedArray)
    
            } catch {
                print("Error: \(error)")
            }
        }
    }
    
    SubClassesCodable.test()
    

    【讨论】:

      猜你喜欢
      • 2017-05-14
      • 1970-01-01
      • 2018-06-21
      • 1970-01-01
      • 2017-12-16
      • 1970-01-01
      • 2013-08-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多