简体   繁体   English

Swift 枚举不同类型的关联值

[英]Swift enum associated value with different types

I have a Swift enum like this:我有一个像这样的 Swift 枚举:

public enum AnimationType {

    case position(Float)
    case position([Keyframe<Float>])
    case scale(Float)
    case scale([Keyframe<Float>])
    case rect(CGRect)
    case rect([Keyframe<CGRect>])
    case transform(CGAffineTransform)
    case transform([Keyframe<CGAffineTransform>])
    ...
    ...
}

As we can see, for each type there are two values possible - fixed value of type T or an array of keyframes with value type T ([Keyframe]).正如我们所见,对于每种类型,都有两个可能的值 - 类型 T 的固定值或具有值类型 T ([Keyframe]) 的关键帧数组。 I am wondering if there is anything I can do to avoid repetition of same name in the enum and merge the two enum case types?我想知道是否有什么办法可以避免在枚举中重复相同的名称并合并两个枚举大小写类型? Or I am modelling it wrong way?或者我以错误的方式建模?

I would solve this with a Kind enum type, for each kind of variation.对于每种变体,我会用Kind枚举类型解决这个问题。

public enum AnimationType {
    public enum Kind<Value> {
        case scalar(Value)
        case keyframes([Keyframe<Value>])
    }

    case position(Kind<Float>)
    case scale(Kind<Float>)
    case rect(Kind<CGRect>)
    case transform(Kind<CGAffineTransform>)
}

Usage:用法:

let anim1 = AnimationType.position(.scalar(10))
let anim2 = AnimationType.position(.keyframes([Keyframe(10)]))

Getting values:获取值:

switch anim1 {
case .position(let kind):
    switch kind {
    case .scalar(let value):
        print("value: \(value)")
    case .keyframes(let keyframes):
        print("keyframes: \(keyframes)")
    }

default: // You would implement the rest
    break
}
switch anim1 {
case .position(.scalar(let value)):
    print("value: \(value)")

case .position(.keyframes(let keyframes)):
    print("keyframes: \(keyframes)")

default: // You would implement the rest
    break
}
if case .position(.scalar(let value)) = anim1 {
    print("value: \(value)")
}

You can also add Codable conformance:您还可以添加Codable一致性:

public struct Keyframe<Value: Codable> {
    let value: Value

    init(_ value: Value) {
        self.value = value
    }
}

extension Keyframe: Codable {
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(value)
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        value = try container.decode(Value.self)
    }
}
public enum AnimationType {
    public enum Kind<Value: Codable> {
        case scalar(Value)
        case keyframes([Keyframe<Value>])
    }

    case position(Kind<Float>)
    case scale(Kind<Float>)
    case rect(Kind<CGRect>)
    case transform(Kind<CGAffineTransform>)
}

extension AnimationType.Kind: Codable {
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()

        switch self {
        case .scalar(let value): try container.encode(value)
        case .keyframes(let keyframes): try container.encode(keyframes)
        }
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()

        if let scalar = try? container.decode(Value.self) {
            self = .scalar(scalar)
            return
        }
        if let keyframes = try? container.decode([Keyframe<Value>].self) {
            self = .keyframes(keyframes)
            return
        }

        // You should throw error here instead
        fatalError("Failed to decode")
    }
}

extension AnimationType: Codable {
    private enum CodingKeys: CodingKey {
        case position
        case scale
        case rect
        case transform
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        switch self {
        case .position(let kind): try container.encode(kind, forKey: .position)
        case .scale(let kind): try container.encode(kind, forKey: .scale)
        case .rect(let kind): try container.encode(kind, forKey: .rect)
        case .transform(let kind): try container.encode(kind, forKey: .transform)
        }
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        if let position = try? container.decode(Kind<Float>.self, forKey: .position) {
            self = .position(position)
            return
        }
        if let scale = try? container.decode(Kind<Float>.self, forKey: .scale) {
            self = .scale(scale)
            return
        }
        if let rect = try? container.decode(Kind<CGRect>.self, forKey: .rect) {
            self = .rect(rect)
            return
        }
        if let transform = try? container.decode(Kind<CGAffineTransform>.self, forKey: .transform) {
            self = .transform(transform)
            return
        }

        // You should throw error here instead
        fatalError("Failed to decode")
    }
}

Example encoding:示例编码:

do {
    let data = try JSONEncoder().encode(anim1)
    if let str = String(data: data, encoding: .utf8) {
        print(str)
        // Prints: {"position":10}
    }
} catch {
    print(error)
}

The same sort of thing with anim2 returns {"position":[10]} .anim2相同的事情返回{"position":[10]}

@George's answer did solve this. @George 的回答确实解决了这个问题。

Swift's solution is of variety. Swift 的解决方案多种多样。

Here is my proposal:这是我的建议:

public struct Keyframe<T> {
    let v : T
}

public enum AnimaKind{
    case simple
    case series
}

public enum AnimatationType {
    case position
    case scale
    case rect
    case transform
}

extension AnimatationType{
    
    func simple<T>(info: T) -> (type: AnimatationType, kind: AnimaKind, info: T){
        return (self, .simple, info)
    }
    
    func series<T>(info: [T]) -> (type: AnimatationType, kind: AnimaKind, info: [Keyframe<T>]){
        let result = info.map { x in Keyframe(v: x) }
        return (self, .series, result)
    }
}

as you can see, the way you unwrap is easier如您所见,打开包装的方式更容易

func test(){
    let animaTest = AnimatationType.position.simple(info: Float(10))
}

you get value from animaTest is convenient,你从animaTest获得价值很方便,

tuple vs enum nested tupleenum nested

I assume that at the lower end you are using some common code to exploit the two types, so you can benefit polymorphism by grouping them under some protocol:我假设在低端您使用一些通用代码来利用这两种类型,因此您可以通过将它们分组到某种协议下来使多态受益:

public enum AnimatationType {
    case position(PositionProtocol)
    case scale(ScaleProtocol)
    case rect(RectProtocol)
    case transform(TransformProtocol)
    ...
    ...
}

And simply extend the types:并简单地扩展类型:

extension Float: PositionProtocol {
     func someCommonGround() -> SomeCommonType { ... }
}

extension Keyframe: PositionProtocol where KeyframeGenericArgument == Float {
     func someCommonGround() -> SomeCommonType { ... }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM