代码之家  ›  专栏  ›  技术社区  ›  Michał Ziobro

Swift嵌套可选值类型(结构)和属性修改

  •  1
  • Michał Ziobro  · 技术社区  · 6 年前

    我在模型中使用了几种值类型,这种值类型(结构)具有嵌套其他值类型(结构)的属性。然后,在另一个结构中的一些嵌套结构中拥有我想要修改(添加、删除、更新)属性的根对象。此外,此属性通常具有可选类型,并且可以为零。因此,对于赋值给var时的值类型,let被复制,我不能使用此内部结构实例的可选Bing并在以后修改它们。因此,我进行此修改的唯一方法如下所示:

    if let cleaningDetails = initialPackage?.cleaningsSchedule?.details?[indexPath.row], cleaningDetails.startTimes == nil {
                            initialPackage?.cleaningsSchedule?.details?[indexPath.row].startTimes = []
                        }
    

    因此,它确实是使用值类型时的唯一选项。其他解决方案有哪些?更改为类(引用类型)-这个函数型、值类型编程真的有那么好吗?或者我应该在这个结构上使用更多的变异函数来方便修改吗?

    2 回复  |  直到 6 年前
        1
  •  1
  •   Rob Napier    6 年前

    首先,您应该减少系统中选项的数量。有多种方法可以处理可选集合(例如,像您建议的那样改变助手方法),但可选的过度使用会造成许多不必要的复杂性。任何类型的集合都应该是可选的,这是非常罕见的。只有当 nil “空”意味着不同的东西(这是非常罕见的)。

    与其将整个数据模型包装在特定的JSON API上,不如将JSON转换为所需的数据模型。例如,这里有一个JSON模型,它包含一个必需的Int,可能包含也可能不包含数组,但在内部,我们希望将“缺少的数组”视为“空”我们还希望在发送空数组之前剥离它们。

    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 并且可以重新序列化,但程序的其余部分从未看到可选的。

    另一种常见的技术是创建一个适配器层,将“JSON兼容”结构转换为内部数据模型并返回。如果方便的话,这允许您的内部数据模型与JSON略有不同。

    当然,您也可以创建helper方法,但所有这些的真正关键是不允许Optionals泄漏到程序的其他部分,而这些部分实际上不是可选的。如果系统中的某个地方一定存在复杂性,请将其放在解析/编码点,而不是使用点。

        2
  •  0
  •   Kunal_D    6 年前

    您也可以始终为optionals提供初始值,只需尝试声明如下数组 var array: [Element]? = [] 这样,您就不必检查nil值。您可以使用动态绑定来使用其他条件

    推荐文章