Swift 嵌套的可选值类型(结构)和属性修改
Swift nested optional value types (structs) and modification of properties
我在我的模型中使用了几种值类型,并且这种值类型(结构)具有嵌套其他值类型(结构)的属性。然后我想在另一个结构中的某些嵌套结构中修改(添加、删除、更新)属性 根对象。此外,此属性通常具有可选类型,可以为 nil。因此,当分配给 var 时,作为值类型,let 被复制,我不能使用此内部结构实例的可选绑定并在以后修改它们。所以我必须做这个修改的唯一方法如下:
if let cleaningDetails = initialPackage?.cleaningsSchedule?.details?[indexPath.row], cleaningDetails.startTimes == nil {
initialPackage?.cleaningsSchedule?.details?[indexPath.row].startTimes = []
}
所以它确实是使用值类型时的唯一选择。还有哪些其他解决方案?更改为 类(引用类型)- 这种功能性的值类型编程真的那么棒吗?或者我应该在这个结构上使用更多的变异函数来促进修改?
您也可以随时为可选值提供初始值,只需尝试像 var array: [Element]? = []
这样声明数组,这样您就不必检查 nil 值。您可以使用动态绑定来使用其他条件
首先,你应该减少系统中Optionals的数量。有多种方法可以处理 Optional-collections(例如像您建议的那样改变辅助方法),但是 Optional-overuse 会产生很多不必要的复杂性。任何类型的 Collection 都应该是 Optional 是非常罕见的。这只有在 nil
和 "empty" 表示不同的东西时才有意义(而且这种情况非常罕见)。
而是围绕特定 JSON API 包装整个数据模型,将 JSON 转换为您想要的数据模型。例如,这里有一个 JSON 模型,它包含一个必需的 Int 并且可能包含也可能不包含一个数组,但在内部我们希望将 "missing array" 视为 "empty." 我们还希望去除空数组在发送之前。
import Foundation
let json = Data("""
{
"y": 1
}
""".utf8)
struct X {
var y: Int
var z: [String]
}
extension X: Codable {
enum CodingKeys: String, CodingKey {
case y, z
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
y = try container.decode(Int.self, forKey: .y)
z = try container.decodeIfPresent([String].self, forKey: .z) ?? []
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(y, forKey: .y)
if !z.isEmpty {
try container.encode(z, forKey: .z)
}
}
}
let decoder = JSONDecoder()
print(try decoder.decode(X.self, from: json))
let encoder = JSONEncoder()
print(String(data: try encoder.encode(X(y: 1, z: [])), encoding: .utf8)!)
这会将所有工作转移到两个方法中,而不是在您每次访问数据模型时将其分散到整个程序中。自定义编码器编写起来仍然有点乏味(并且会在编码器步骤中引入细微的错误),所以如果你有很多它们,你应该看看 SwiftGen 可以为你编写它们。
如果你真的想跟踪一个键是丢失还是空(这样你就可以按照发送给你的方式重新编码),那么我可能会这样隐藏可选属性:
struct X: Codable{
enum CodingKeys: String, CodingKey {
case y
case _z = "z"
}
var y: Int
private var _z: [String]? // The actual `z` we got from the JSON
var z: [String] { get { return _z ?? [] } set { _z = newValue } }
init(y: Int, z: [String]?) {
self.y = y
self._z = z
}
}
"real" z
存储在 _z
中并且可以重新序列化,但程序的其余部分永远看不到 Optional。
另一种比较常见的技术是创建一个适配器层,将 "JSON-compatible" 结构转换为内部数据模型并返回。如果方便的话,这允许您的内部数据模型与 JSON 略有不同。
您当然也可以创建辅助方法,但所有这一切的真正关键是不允许 Optionals 泄漏到程序的其他部分,这些部分并不是真正可选的。如果系统中某处必须有复杂性,请将其放在 parsing/encoding 点,而不是使用点。
我在我的模型中使用了几种值类型,并且这种值类型(结构)具有嵌套其他值类型(结构)的属性。然后我想在另一个结构中的某些嵌套结构中修改(添加、删除、更新)属性 根对象。此外,此属性通常具有可选类型,可以为 nil。因此,当分配给 var 时,作为值类型,let 被复制,我不能使用此内部结构实例的可选绑定并在以后修改它们。所以我必须做这个修改的唯一方法如下:
if let cleaningDetails = initialPackage?.cleaningsSchedule?.details?[indexPath.row], cleaningDetails.startTimes == nil {
initialPackage?.cleaningsSchedule?.details?[indexPath.row].startTimes = []
}
所以它确实是使用值类型时的唯一选择。还有哪些其他解决方案?更改为 类(引用类型)- 这种功能性的值类型编程真的那么棒吗?或者我应该在这个结构上使用更多的变异函数来促进修改?
您也可以随时为可选值提供初始值,只需尝试像 var array: [Element]? = []
这样声明数组,这样您就不必检查 nil 值。您可以使用动态绑定来使用其他条件
首先,你应该减少系统中Optionals的数量。有多种方法可以处理 Optional-collections(例如像您建议的那样改变辅助方法),但是 Optional-overuse 会产生很多不必要的复杂性。任何类型的 Collection 都应该是 Optional 是非常罕见的。这只有在 nil
和 "empty" 表示不同的东西时才有意义(而且这种情况非常罕见)。
而是围绕特定 JSON API 包装整个数据模型,将 JSON 转换为您想要的数据模型。例如,这里有一个 JSON 模型,它包含一个必需的 Int 并且可能包含也可能不包含一个数组,但在内部我们希望将 "missing array" 视为 "empty." 我们还希望去除空数组在发送之前。
import Foundation
let json = Data("""
{
"y": 1
}
""".utf8)
struct X {
var y: Int
var z: [String]
}
extension X: Codable {
enum CodingKeys: String, CodingKey {
case y, z
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
y = try container.decode(Int.self, forKey: .y)
z = try container.decodeIfPresent([String].self, forKey: .z) ?? []
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(y, forKey: .y)
if !z.isEmpty {
try container.encode(z, forKey: .z)
}
}
}
let decoder = JSONDecoder()
print(try decoder.decode(X.self, from: json))
let encoder = JSONEncoder()
print(String(data: try encoder.encode(X(y: 1, z: [])), encoding: .utf8)!)
这会将所有工作转移到两个方法中,而不是在您每次访问数据模型时将其分散到整个程序中。自定义编码器编写起来仍然有点乏味(并且会在编码器步骤中引入细微的错误),所以如果你有很多它们,你应该看看 SwiftGen 可以为你编写它们。
如果你真的想跟踪一个键是丢失还是空(这样你就可以按照发送给你的方式重新编码),那么我可能会这样隐藏可选属性:
struct X: Codable{
enum CodingKeys: String, CodingKey {
case y
case _z = "z"
}
var y: Int
private var _z: [String]? // The actual `z` we got from the JSON
var z: [String] { get { return _z ?? [] } set { _z = newValue } }
init(y: Int, z: [String]?) {
self.y = y
self._z = z
}
}
"real" z
存储在 _z
中并且可以重新序列化,但程序的其余部分永远看不到 Optional。
另一种比较常见的技术是创建一个适配器层,将 "JSON-compatible" 结构转换为内部数据模型并返回。如果方便的话,这允许您的内部数据模型与 JSON 略有不同。
您当然也可以创建辅助方法,但所有这一切的真正关键是不允许 Optionals 泄漏到程序的其他部分,这些部分并不是真正可选的。如果系统中某处必须有复杂性,请将其放在 parsing/encoding 点,而不是使用点。