Swift - Associated Value or Extension for an Enum

Swift - Associated value or extension for an Enum

Unfortunately you cannot define static properties based on enum cases, but you can use computed properties and switch to return values for each case:

enum Icon {
case plane
case arrow
case logo
case flag

var image: UIImage {
switch self {
case .plane: return UIImage(named: "plane.png")!
case .arrow: return UIImage(named: "arrow.png")!
case .logo: return UIImage(named: "logo.png")!
case .flag: return UIImage(named: "flag.png")!
}
}

var color: UIColor {
switch self {
case .plane: return UIColor.greenColor()
case .arrow: return UIColor.greenColor()
case .logo: return UIColor.greenColor()
case .flag: return UIColor.greenColor()
}
}
}

// usage
Icon.plane.color

Is it possible to add associated values to an existing Swift enum via extensions?

No you can't. Think about it, the currentState enum really is just an restricted Int. It's not even an object.

If you need a more complex enum, you need to wrap the one given to you.

Note: Swift extensions cannot add state to the base type in general. E.g. this doesn't work either:

class A {}
extension A { var value : Int = 32 }

How to test equality of Swift enums with associated values

Swift 4.1+

As @jedwidz has helpfully pointed out, from Swift 4.1 (due to SE-0185, Swift also supports synthesizing Equatable and Hashable for enums with associated values.

So if you're on Swift 4.1 or newer, the following will automatically synthesize the necessary methods such that XCTAssert(t1 == t2) works. The key is to add the Equatable protocol to your enum.

enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)

Before Swift 4.1

As others have noted, Swift doesn't synthesize the necessary equality operators automatically. Let me propose a cleaner (IMHO) implementation, though:

enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}

public func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
switch (lhs, rhs) {
case let (.Name(a), .Name(b)),
let (.Number(a), .Number(b)):
return a == b
default:
return false
}
}

It's far from ideal — there's a lot of repetition — but at least you don't need to do nested switches with if-statements inside.

How to get a swift enum's associated value regardless of the enum case

Define a method isMissing() inside the enum - write it once and only once. Then you get nearly exactly what you prefer:

for field in self.fields {
if field.value.isMissing() {
missingFields.append(field.name)
}
}

It would look something like this (from the Swift Interpreter):

  1> class Foo {}
>
2> enum Value {
3. case One(Foo!)
4. case Two(Foo!)
5.
6. func isMissing () -> Bool {
7. switch self {
8. case let .One(foo): return foo == nil
9. case let .Two(foo): return foo == nil
10. }
11. }
12. }
13> let aVal = Value.One(nil)
aVal: Value = One {
One = nil
}
14> aVal.isMissing()
$R0: Bool = true

Comparing two enum variables regardless of their associated values

Updated approach:

I think there's no native support for this. But you can achieve it by defining a custom operator (preferrably by using a protocol, but you can do it directly as well). Something like this:

protocol EnumTypeEquatable {
static func ~=(lhs: Self, rhs: Self) -> Bool
}

extension DataType: EnumTypeEquatable {
static func ~=(lhs: DataType, rhs: DataType) -> Bool {
switch (lhs, rhs) {
case (.one, .one),
(.two, .two):
return true
default:
return false
}
}
}

And then use it like:

let isTypeEqual = DataType.One(value: 1) ~= DataType.One(value: 2)
print (isTypeEqual) // true


Old approach:

protocol EnumTypeEquatable {
var enumCaseIdentifier: String { get }
}

extension DataType: EnumTypeEquatable {
var enumCaseIdentifier: String {
switch self {
case .one: return "ONE"
case .two: return "TWO"
}
}
}

func ~=<T>(lhs: T, rhs: T) -> Bool where T: EnumTypeEquatable {
return lhs.enumCaseIdentifier == rhs.enumCaseIdentifier
}

The older version depends on Runtime and might be provided with default enumCaseIdentifier implementation depending on String(describing: self) which is not recommended. (since String(describing: self) is working with CustromStringConvertible protocol and can be altered)

How to make a Swift enum with associated values equatable

SE-0185 Synthesizing Equatable and Hashable conformance has been implemented in Swift 4.1, so that it suffices do declare conformance to the protocol (if all members are Equatable):

enum ViewModel: Equatable {
case heading(String)
case options(id: String, title: String, enabled: Bool)
}

For earlier Swift versions, a convenient way is to use that tuples can be compared with ==.

You many also want to enclose the compatibility code in a Swift version check, so that the automatic synthesis is used once the project is updated to Swift 4.1:

enum ViewModel: Equatable {
case heading(String)
case options(id: String, title: String, enabled: Bool)

#if swift(>=4.1)
#else
static func ==(lhs: ViewModel, rhs: ViewModel) -> Bool {
switch (lhs, rhs) {
case (let .heading(lhsString), let .heading(rhsString)):
return lhsString == rhsString
case (let .options(lhsId, lhsTitle, lhsEnabled), let .options(rhsId, rhsTitle, rhsEnabled)):
return (lhsId, lhsTitle, lhsEnabled) == (rhsId, rhsTitle, rhsEnabled)
default:
return false
}
}
#endif
}

Is it possible to store pattern of enum with associated value in array?

If instantiating the enum with a default (or garbage) value isn't a big deal

enum MessageType {
case audio(String)
case photo
case text
}

protocol SneakyEquatableMessage {
func equals(message: MessageType) -> Bool
}

extension MessageType: SneakyEquatableMessage {
func equals(message: MessageType) -> Bool {
switch (self, message) {
case (.audio(_), .audio(_)),
(.photo, .photo),
(.text, .text):
return true
default:
return false
}
}
}

class Handler {
let allowed: [MessageType]

init(_ allowed: [MessageType]) { self.allowed = allowed }

func canHandle(_ messageType: MessageType) -> Bool {
return allowed.contains { $0.equals(message: messageType) }
}
}

Basic Usage

let handler = Handler([.audio(""), .photo])
print(handler.canHandle(.text)) // Prints false
print(handler.canHandle(.audio("abc")) //Prints true


Default (or garbage) values are unrealistic

This particular section is more specific in this context, but ultimately you're going to have breakdown your enum somehow as of Swift 4. So this is my suggestion: Dependency Injection of the Factory Pattern inside Handler. This solves all of your problems pretty cleanly without having to touch switch within Handler or optionals.

enum DisassembledMessage {
case audio
case photo
case text
}

protocol MessageTypeFactory {
func disassemble(message: MessageType) -> DisassembledMessage
func disassemble(messages: [MessageType]) -> [DisassembledMessage]
}

class Handler {
let allowed: [MessageType]
let factory: MessageTypeFactory

init(allowed: [MessageType], with factory: MessageTypeFactory) {
self.allowed = allowed
self.factory = factory
}

func canHandle(_ messageType: DisassembledMessage) -> Bool {
return factory
.disassemble(messages: allowed)
.contains { $0 == messageType }
}
}

Basic Usage

let audioValue: Audio = //...
let audioMessage = MessageType.audio(audioValue)
let factory: MessageTypeFactory = //...
let handler = Handler(allowed: [audioMessage, .photo], with: factory)
print(handler.canHandle(.text)) // Prints false
print(handler.canHandle(factory.disassemble(message: audioMessage))) //Prints true

You may be asking: wait... you just created another enum (also this is just an example, you could convert it to whatever you want in that protocol). Well I say: the enum you're using is from a library... see my notes section. Also, you can now use that factory anywhere you need to break down the library type to something, including inside Handler. You could easily expand the protocol MessageTypeFactory to convert your enum to other types (hopefully behaviors) you've created, and basically just distance yourself from the library type when you need to. I hope this helps clarify what I was getting at! I don't even think you should store MessageType in your class. You should store your own type which is some kind of mapped version of MessageType, like DisassembledType.

I hope this helps!


Notes

A few things:

  • I'm sorry your soul is owned by a library, really.
  • Use the Adapter Pattern. Clean C++ is one of many places you
    can learn about it. Don't pollute your entire code base with one of
    their types! That's just a tip.
  • I know you said you didn't want to use a switch... but you're working with enums... At least it's within the enum!
  • Use your own types! (Did I say that?)
  • Use the Adapter Pattern! (Stop it.)


Related Topics



Leave a reply



Submit