Swift 枚举不同类型的关联值
Swift enum associated value with different types
我有一个这样的 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])。我想知道我是否可以做些什么来避免在枚举中重复相同的名称并合并两个枚举案例类型?或者我建模的方式不对?
我会用 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
returns {"position":[10]}
.
同样的事情
@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
获取价值很方便,
tuple
对比 enum nested
我假设您在低端使用一些通用代码来利用这两种类型,因此您可以通过在某种协议下对它们进行分组来受益于多态性:
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 { ... }
}
我有一个这样的 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])。我想知道我是否可以做些什么来避免在枚举中重复相同的名称并合并两个枚举案例类型?或者我建模的方式不对?
我会用 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
returns {"position":[10]}
.
@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
获取价值很方便,
tuple
对比 enum nested
我假设您在低端使用一些通用代码来利用这两种类型,因此您可以通过在某种协议下对它们进行分组来受益于多态性:
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 { ... }
}