【问题标题】:Swift enum associated value with different typesSwift枚举与不同类型的关联值
【发布时间】:2021-11-01 07:02:40
【问题描述】:

我有一个这样的 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>])
    ...
    ...
}

正如我们所见,每种类型都有两个可能的值 - T 类型的固定值或值类型为 T 的关键帧数组([Keyframe])。我想知道是否有什么办法可以避免在枚举中重复同名并合并两个枚举案例类型?还是我的建模方式不对?

【问题讨论】:

    标签: ios swift enums swift5


    【解决方案1】:

    我假设您在低端使用一些通用代码来利用这两种类型,因此您可以通过将它们分组到某个协议下来受益于多态性:

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

    然后简单地扩展类型:

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

    【讨论】:

      【解决方案2】:

      我会使用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>)
      }
      

      用法:

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

      获取值:

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

      您还可以添加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")
          }
      }
      

      示例编码:

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

      anim2 相同的事情返回{"position":[10]}

      【讨论】:

      • 这很酷,但是如何测试代码中参数的类型(无论是标量还是关键帧)?每次都需要用switch语句吗?
      • 真的。切换开关
      • @DeepakSharma 更新了我关于如何获取值的答案。您还可以在 AnimationType 枚举上创建一个计算属性来读取这些值,如果这对您来说更容易的话。
      • @George 这很好,现在下一个挑战是如何使这个枚举 Kind 确认到 Codable ,因为 Value 是 Codable 。因为没有这个,我需要在每个 switch 语句中重新编码关键帧。
      • @DeepakSharma Codable 要好得多。当您对 JSON 进行编码时,它会直接变成您的 AnimationType 结构(而不​​仅仅是具有相同键的字典)。 JSON 也是一种标准格式,更易于与 API 等各种事物一起使用。我 100% 推荐 Codable 和 JSON,而不是像 NSCoding 这样的旧方法。 Codable 比任何其他方法都更容易、更安全,并且具有更好的互操作性。
      【解决方案3】:

      @George 的回答确实解决了这个问题。

      Swift 的解决方案多种多样。

      这是我的建议:

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

      如您所见,打开包装的方式更容易

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

      你从animaTest获得价值很方便,

      tupleenum nested

      【讨论】:

      • 如何使用此代码识别给定 AnimateType 对象中的参数类型(简单或系列)?
      • (type: AnimatationType, kind: AnimaKind, info: [Keyframe&lt;T&gt;]) :你想要的AnimatationType,简单的标量或系列[Frame],简单的标量值或系列[Frame]值。可以直接使用
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-09
      • 2020-09-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多