在 UserDefaults 中保存不同对象类型的数组
Posted
技术标签:
【中文标题】在 UserDefaults 中保存不同对象类型的数组【英文标题】:Save array of different object types in 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 []
谢谢
【问题讨论】:
【参考方案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()
【讨论】:
以上是关于在 UserDefaults 中保存不同对象类型的数组的主要内容,如果未能解决你的问题,请参考以下文章
将对象保存到确认协议的 UserDefaults(使用面向协议的编程范例)