swift - Swift 嵌套可选值类型(结构)和属性修改
问题描述
我在我的模型中使用了几种值类型,并且这种值类型(结构)具有嵌套其他值类型(结构)的属性。然后拥有根对象,我想在另一个结构中的一些嵌套结构中修改(添加、删除、更新)属性。此外,此属性通常具有可选类型并且可以为 nil。因此,当分配给 var 时,让值类型被复制,我不能使用这个内部结构实例的可选绑定并在以后修改它们。因此,我必须进行此修改的唯一方法如下:
if let cleaningDetails = initialPackage?.cleaningsSchedule?.details?[indexPath.row], cleaningDetails.startTimes == nil {
initialPackage?.cleaningsSchedule?.details?[indexPath.row].startTimes = []
}
因此,在使用值类型时,它确实是唯一的选择。其他解决方案是什么?更改为类(引用类型)——这种函数式、值类型的编程真的那么棒吗?或者我应该在这个结构上使用更多的变异函数来促进修改?
解决方案
首先,您应该减少系统中 Optional 的数量。有多种方法可以处理 Optional-collections(例如,像您建议的那样改变辅助方法),但是 Optional-overuse 会产生很多不必要的复杂性。任何类型的 Collection 都应该是 Optional 是非常罕见的。只有当和“空”意味着不同的东西时,这才有意义nil
(这是非常罕见的)。
与其将整个数据模型包装在特定的 JSON API 周围,不如将 JSON 转换为您想要的数据模型。例如,这是一个 JSON 模型,它包含一个必需的 Int,可能包含也可能不包含一个 Array,但在内部,我们希望将“丢失的数组”视为“空”。我们还想在发送空数组之前去掉它们。
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
}
}
“真实”z
存储在_z
其中并且可以重新序列化,但程序的其余部分永远不会看到 Optional。
另一种比较常见的技术是创建一个适配器层,将“JSON 兼容”结构转换为内部数据模型并返回。如果方便的话,这允许您的内部数据模型与 JSON 稍有不同。
当然,您也可以创建辅助方法,但所有这一切的真正关键是不允许 Optionals 泄漏到程序的其余部分,而这些部分并不是真正的可选。如果系统中某处必须存在复杂性,请将其放在解析/编码点,而不是使用点。