【问题标题】:How to extend float3 or any other built-in type to conform to the Codable protocol?如何扩展 float3 或任何其他内置类型以符合 Codable 协议?
【发布时间】:2018-01-29 01:45:42
【问题描述】:

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

我尝试按照Encoding and Decoding Custom Types 中的建议编写一个基本扩展,如下所示,但是对于 init 中的每个分配行都会呈现错误 'self' used before all stored properties are initialized。我认为这是因为编译器不确定 Float.self 是在 float3 初始化之前定义的,但我不确定如何解决这个问题。

此外,初始化的末尾说Return from initializer without initializing all stored properties,我认为这意味着除了x、y和z之外还有float3属性,但我想知道是否有办法默认/忽略这些,和/或者如何找到完整的属性列表,除了挖掘 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 结构声明在哪里?这些 x、y、z 是您的自定义类型中声明的唯一属性吗?请注意,以大写字母 CamelCase 开头的类、结构、枚举和协议的命名是 Swift 命名约定
  • @LeoDabus 我认为 float3 结构一定是 obj-c 的遗留物——这不是我定义的,但似乎是在 simd 文件中定义的。
  • 需要导入和框架吗?
  • 尝试在let values ...之前添加self.init()
  • 天啊...谢谢!我以为我已经尝试过了,但意识到我试图初始化超类(obv. can't on struct)

标签: swift initialization scenekit swift4 codable


【解决方案1】:

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

正如 Rob 在 his answer 中提到的,问题的原因与 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))
}

【讨论】:

  • 可以在初始化器try self.init(values.decode(Float.self, forKey: .x), values.decode(Float.self, forKey: .y), values.decode(Float.self, forKey: .z))之前使用一次尝试
  • @LeoDabus 感谢您的提示,在我的回答中包含了较短的版本,但也保留了原始版本,因为有些人可能更喜欢可读性而不是简洁的代码。
  • 不客气。我还将枚举更改为 case x, y, z 并将其设为私有
  • 解决方案很好,但这不是由于指定的初始化程序。结构没有指定的初始化器或便利初始化器。问题是xyzfloat3 上的计算属性,而不是存储属性,因此您不能在init 期间编写它们。
  • @RobNapier 你完全正确,我错过了float3 实际上是一个结构的事实,所以你的解释确实是正确的。
【解决方案2】:

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]
    }
}

如果您为此编写扩展程序,您将得到同样的错误。

【讨论】:

    【解决方案3】:

    我认为值得一提的是,您可以简单地使用未键入的容器将 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])
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-06
      • 2018-07-12
      • 2013-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-03
      • 1970-01-01
      相关资源
      最近更新 更多