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 点,而不是使用点。