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
Why Function Return Nil Firebase Swift
How to Convert [Any] to Nsarray
Spritekiit Swift: Touch a Moving Object
Firebase Datadescription Returns Empty Array
How to Make a Uiview Grow in Uiview.Animate() with an Accurate Corner Radius
Fetch an Email Body in Mailcore2 Osx with Swift
Swift 2 Unrecognized Selector in Tapgesture
Swift Conforming Multiple Protocols Inherits from Same Protocol with Associated Type
String Nil Inside Nsurlsession
How to Use Querystartingatvalue in My Searchcontroller to Search for Users
What Does the Snapshot/Observer Code Do in Firebase
Stopping Timer at Defined Amount of Time in Swift
Swift Implement Literalconvertible Protocol
How to Handle Unsafepointer<Unmanaged<Cfarray>>
Is There Any Particular Use of Closure in Swift? and What's the Benefit