如何扩展 float3 或任何其他内置类型以符合 Codable 协议?

How to extend float3 or any other built-in type to conform to the Codable protocol?

在尝试使用基本的 JSONEncoder 序列化 float3 对象数组时,发现 float3 不符合 Codable 协议,因此无法完成。

我尝试按照 Encoding and Decoding Custom Types 中的建议编写一个基本扩展,如下所示,但在 init.我假设这是因为编译器不确定 Float.self 是在 float3 初始化之前定义的,但我不确定如何解决这个问题。

此外,init 的结尾说 Return from initializer without initializing all stored properties,我认为这意味着除了 x、y 和 z 之外还有 float3 属性,但我想知道是否有办法 default/ignore 这些,and/or 除了挖掘 simd 中的所有 float3 扩展之外,如何找到完整的属性列表。

如果您对如何进行此操作有任何想法,我们将不胜感激。谢谢!

import SceneKit

extension float3: Codable {
    public init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        x = try values.decode(Float.self, forKey: .x)
        y = try values.decode(Float.self, forKey: .y)
        z = try values.decode(Float.self, forKey: .z)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(x, forKey: .x)
        try container.encode(y, forKey: .y)
        try container.encode(z, forKey: .z)
    }

    enum CodingKeys: String, CodingKey {
        case x
        case y
        case z
    }
}

这是来自 simd 文件的(我认为是完整的)float3 定义:

/// A vector of three `Float`.  This corresponds to the C and
/// Obj-C type `vector_float3` and the C++ type `simd::float3`.
public struct float3 {

    public var x: Float

    public var y: Float

    public var z: Float

    /// Initialize to the zero vector.
    public init()

    /// Initialize a vector with the specified elements.
    public init(_ x: Float, _ y: Float, _ z: Float)

    /// Initialize a vector with the specified elements.
    public init(x: Float, y: Float, z: Float)

    /// Initialize to a vector with all elements equal to `scalar`.
    public init(_ scalar: Float)

    /// Initialize to a vector with elements taken from `array`.
    ///
    /// - Precondition: `array` must have exactly three elements.
    public init(_ array: [Float])

    /// Access individual elements of the vector via subscript.
    public subscript(index: Int) -> Float
}

extension float3 : Equatable {

    /// True iff every element of lhs is equal to the corresponding element of
    /// rhs.
    public static func ==(lhs: float3, rhs: float3) -> Bool
}

extension float3 : CustomDebugStringConvertible {

    /// Debug string representation
    public var debugDescription: String { get }
}

extension float3 : ExpressibleByArrayLiteral {

    /// Initialize using `arrayLiteral`.
    ///
    /// - Precondition: the array literal must exactly three
    ///   elements.
    public init(arrayLiteral elements: Float...)
}

extension float3 : Collection {

    /// The position of the first element in a nonempty collection.
    ///
    /// If the collection is empty, `startIndex` is equal to `endIndex`.
    public var startIndex: Int { get }

    /// The collection's "past the end" position---that is, the position one
    /// greater than the last valid subscript argument.
    ///
    /// When you need a range that includes the last element of a collection, use
    /// the half-open range operator (`..<`) with `endIndex`. The `..<` operator
    /// creates a range that doesn't include the upper bound, so it's always
    /// safe to use with `endIndex`. For example:
    ///
    ///     let numbers = [10, 20, 30, 40, 50]
    ///     if let index = numbers.index(of: 30) {
    ///         print(numbers[index ..< numbers.endIndex])
    ///     }
    ///     // Prints "[30, 40, 50]"
    ///
    /// If the collection is empty, `endIndex` is equal to `startIndex`.
    public var endIndex: Int { get }

    /// Returns the position immediately after the given index.
    ///
    /// The successor of an index must be well defined. For an index `i` into a
    /// collection `c`, calling `c.index(after: i)` returns the same index every
    /// time.
    ///
    /// - Parameter i: A valid index of the collection. `i` must be less than
    ///   `endIndex`.
    /// - Returns: The index value immediately after `i`.
    public func index(after i: Int) -> Int
}

extension float3 {

    /// Vector (elementwise) sum of `lhs` and `rhs`.
    public static func +(lhs: float3, rhs: float3) -> float3

    /// Vector (elementwise) difference of `lhs` and `rhs`.
    public static func -(lhs: float3, rhs: float3) -> float3

    /// Negation of `rhs`.
    prefix public static func -(rhs: float3) -> float3

    /// Elementwise product of `lhs` and `rhs` (A.k.a. the Hadamard or Schur
    /// vector product).
    public static func *(lhs: float3, rhs: float3) -> float3

    /// Scalar-Vector product.
    public static func *(lhs: Float, rhs: float3) -> float3

    /// Scalar-Vector product.
    public static func *(lhs: float3, rhs: Float) -> float3

    /// Elementwise quotient of `lhs` and `rhs`.
    public static func /(lhs: float3, rhs: float3) -> float3

    /// Divide vector by scalar.
    public static func /(lhs: float3, rhs: Float) -> float3

    /// Add `rhs` to `lhs`.
    public static func +=(lhs: inout float3, rhs: float3)

    /// Subtract `rhs` from `lhs`.
    public static func -=(lhs: inout float3, rhs: float3)

    /// Multiply `lhs` by `rhs` (elementwise).
    public static func *=(lhs: inout float3, rhs: float3)

    /// Divide `lhs` by `rhs` (elementwise).
    public static func /=(lhs: inout float3, rhs: float3)

    /// Scales `lhs` by `rhs`.
    public static func *=(lhs: inout float3, rhs: Float)

    /// Scales `lhs` by `1/rhs`.
    public static func /=(lhs: inout float3, rhs: Float)
}

您可以解决编译器错误,而不是尝试直接将解码值分配给您的类型的字段,将解码值存储在局部变量中,然后调用 float3 的指定初始化程序。

正如 Rob 在 中提到的,问题的原因与 xyz 是计算属性而不是存储属性有关,因此初始化时不能直接写入。

extension float3: Codable {
    public init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        let x = try values.decode(Float.self, forKey: .x)
        let y = try values.decode(Float.self, forKey: .y)
        let z = try values.decode(Float.self, forKey: .z)
        self.init(x, y, z)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(x, forKey: .x)
        try container.encode(y, forKey: .y)
        try container.encode(z, forKey: .z)
    }

    private enum CodingKeys: String, CodingKey {
        case x,y,z
    }
}

您可以使用以下代码在 Playground 中测试编码和解码:

let vector = float3(3, 2.4, 1)
do {
    let encodedVector = try JSONEncoder().encode(vector)
    let jsonVector = String(data: encodedVector, encoding: .utf8) //"{"x":3,"y":2.4000000953674316,"z":1}"
    let decodedVector = try JSONDecoder().decode(float3.self, from: encodedVector) //float3(3.0, 2.4, 1.0)
} catch {
    print(error)
}

如果您喜欢更简洁的代码,init(from decoder:) 方法可以缩短为:

public init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    try self.init(values.decode(Float.self, forKey: .x), values.decode(Float.self, forKey: .y), values.decode(Float.self, forKey: .z))
}

Dávid 关于如何解决问题是正确的,但值得理解为什么它是一个问题(它实际上没有指定 vs 便利初始化程序;仅适用于 类)。

如果我们创建了我们自己的 float3 版本,您的代码可以在扩展名下正常工作:

struct float3 {
    var x: Float
    var y: Float
    var z: Float
}

extension float3: Codable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        x = try values.decode(Float.self, forKey: .x)
        y = try values.decode(Float.self, forKey: .y)
        z = try values.decode(Float.self, forKey: .z)
    }
    // ...
}

所以这看起来很奇怪。为什么我们对 simd 类型的实现与 simd 类型的工作方式不同?因为 simd 没有 x 存储 属性(或 yz)。这些都是计算属性。

真正的接口在simd.swift.gyb中定义。如果您研究该代码,您会看到所有 SIMD 向量类型都使用通用 _vector 存储:

public var _vector: Builtin.${llvm_vectype}

然后为每个字母定义了计算属性(component['x','y','z','w']):

% for i in xrange(count):
  public var ${component[i]} : ${scalar} {
    @_transparent
    get {
      let elt = Builtin.${extractelement}(_vector,
        (${i} as Int32)._value)

      return ${scalar}(_bits: elt)
    }
    @_transparent
    set {
      _vector = Builtin.${insertelement}(_vector,
        newValue._value,
        (${i} as Int32)._value)
    }
  }
% end

因此,如果我们要构建自己的 float3(没有花哨的内置函数),它会是这样的:

struct float3 {
    private var vector: [Float]
    var x: Float { get { return vector[0] } set { vector[0] = newValue } }
    var y: Float { get { return vector[1] } set { vector[1] = newValue } }
    var z: Float { get { return vector[2] } set { vector[2] = newValue } }
    init(x: Float, y: Float, z: Float) {
        vector = [x, y, z]
    }
}

如果你针对它编写你的扩展,你会得到同样的错误。

我认为值得一提的是,您可以简单地使用一个未加密的容器来 encode/decode 您的 float3 属性作为数组:

extension float3: Codable {
    public init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        try self.init(container.decode([Float].self))
    }
    public func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        try container.encode([x,y,z])
    }
}