在 UserDefaults 中保存不同 object 类型的数组
Save array of different object types in UserDefaults
我有两个 class,广播和播客,children 媒体 class。
我正在尝试将一系列媒体(包括收音机和播客)保存到 UserDefaults,但是当我取回它时,我只有媒体(我丢失了收音机或播客的信息)。
我无法将项目投射到广播或播客。
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 []
}
谢谢
此问题与 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 = [=11=] as? Radio {
print(asRadio)
}else if let asPodcast = [=11=] as? Podcast {
print(asPodcast)
} else {
print("Nop: \([=11=])")
}
}
//This is a working solution
let mixedDecoded = try decoder.decode([Mixed].self, from: encodedArray)
let decodedArray: [Media] = mixedDecoded.map {
switch [=11=] {
case .radio(let radio):
return radio
case .podcast(let podcast):
return podcast
}
}
print(decodedArray)
} catch {
print("Error: \(error)")
}
}
}
SubClassesCodable.test()
我有两个 class,广播和播客,children 媒体 class。 我正在尝试将一系列媒体(包括收音机和播客)保存到 UserDefaults,但是当我取回它时,我只有媒体(我丢失了收音机或播客的信息)。 我无法将项目投射到广播或播客。
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 []
}
谢谢
此问题与 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 = [=11=] as? Radio {
print(asRadio)
}else if let asPodcast = [=11=] as? Podcast {
print(asPodcast)
} else {
print("Nop: \([=11=])")
}
}
//This is a working solution
let mixedDecoded = try decoder.decode([Mixed].self, from: encodedArray)
let decodedArray: [Media] = mixedDecoded.map {
switch [=11=] {
case .radio(let radio):
return radio
case .podcast(let podcast):
return podcast
}
}
print(decodedArray)
} catch {
print("Error: \(error)")
}
}
}
SubClassesCodable.test()