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

两盒软妹~` 提交于 2019-12-01 20:06:13

You can solve the compiler error by instead of trying to directly assign the decoded values to the fields of your type, storing the decoded values in local variables, then calling a designated initializer of float3.

As Rob mentions in his answer, the cause of the issue has to do with x, y and z being computed properties rather than stored ones, so they cannot be directly written during initialization.

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

You can test both encoding and decoding in a Playground using below code:

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

If you prefer more concise code, the init(from decoder:) method can be shortened to:

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 is correct on how to fix the problem, but it's worth understanding why it's a problem (it's not actually designated vs convenience initializers; that only applies to classes).

If we created our own version of float3, your code would work fine with an extension:

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)
    }
    // ...
}

So that seems strange. Why doesn't our implementation of the simd type work the same as the simd type? Because simd doesn't have an x stored property (or y or z). Those are all computed properties.

The real interface is defined in simd.swift.gyb. If you study that code, you'll see all the SIMD vector types use a generic _vector storage:

public var _vector: Builtin.${llvm_vectype}

And then there are computed properties defined for each letter (component is ['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

So if we were building our own float3 (without fancy builtins), it'd be something like this:

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

And if you write your extension against that, you'll get the same error.

I think that it is worth mentioning that you can simply use an unkeyed container to encode/decode your float3 properties as an array:

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])
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!