神刀安全网

Swift Enumerations

Swift Enumerations

Swift Enumerations

In this article we’re going to continue our journey through the Swift programming language by looking at another of it’s features – enumerations.

Enumerations, or ‘enums’ for short, are a common feature in many programming languages providing a convenient way of grouping a set of related values together into a single code construct. As we’ll see though, enumerations in Swift are much more capable than enumerations you may be used to, supporting many of the features that you would normally expect from structs and classes rather than enumerations.

  • What Are Enumerations?
  • Three Types of Swift Enumeration
    • Defining an Enumeration
    • Assigning Enumeration Cases To Variables and Constants
  • Raw Value Enumerations
    • Raw Value Enumeration Basics
    • Implicit Raw Value Assignment
    • Enumeration Cases Versus Raw Values
    • Accessing the Raw Value of an Enumeration Case
    • Enumeration Initialisation Using Raw Values
  • Associated Value Enumerations
    • Defining Enumeration Types with Associated Values
    • Assigning Enumerations Values with Associated Values
  • Enumerations and Pattern Matching
    • Checking Equality in Simple and Raw-Value Enumerations
    • Boolean Comparison of Associated Values Enumerations
  • Enumeration Containment
    • Recursive Enumerations
    • Contained Enumerations
  • Enumeration Methods and Properties
    • Enumeration Instance Properties
    • Enumeration Type Properties
    • Enumeration Instance Methods
    • Mutating Enumeration Instance Methods
    • Enumeration Type Methods
    • Custom Enumeration Initialisers
  • Side Note – Referring To Enumeration Cases
  • Enumerations And Protocols
    • Enumerations and Basic Protocol Conformance
    • Enumerations and Advanced Protocol Conformance
  • Using Enumerations with Extensions
  • Using Enumerations with Generics

To kick off today’s journey, let’s start by looking at what enumerations actually are.

What Are Enumerations?

Think about a traffic light. The colours of a traffic light can be one of three colours – red, amber or green. They can’t be blue, they can’t be purple and they can’t be one of the millions or other colours that humans perceive.

Now imagine we were trying to represent that traffic light in code. How would we go about it?

Maybe we could use strings to represent the names of the different colours something like “Red”, “Amber” or “Green”. That might work but it would be a little error prone if we misspelt something…

What if we encoded the different colours as integer values? Again, possible, but we would have to remember which colour each number represented and what if we accidentally set things to an invalid number?

Neither of those options really work.

What we really want is a way to define a subset of values in a way that that we can understand what each value represents and can also have the compiler check that we are using those values correctly. This is where enumerations come in.

In their simplest forms enumerations are a way of defining new data types to represent a finite group of related values.

Each value within the body of the enumeration is known as an enumeration case . You can think of enumeration cases as defining the different potential values that a value of the enumeration type may take. In our example this might mean the values of the traffic light colours such as red , amber or green .

By grouping the values together as a single type, enumerations allow us to work with those values in type-safe manner and allow the compiler to check that we are using the values correctly.

The concept of enumerations isn’t new. Most programming languages have them in one form or another and if you are coming to Swift from these other languages, Swift’s enumeration types may not appear to be anything particularly new or special. Don’t be fooled though. As we’ll see later in this article, enumerations in Swift are way , way more flexible than you’ll be used to.

Three Types of Swift Enumeration

In Swift, there are three distinct categories of enumeration:

– Basic Enumerations

– Enumerations that have Raw Values

– Enumerations that have Associated Values

For now, don’t worry about the differences (or what raw values or associated values are) as we’ll go over them during the course of this article. The important thing to take on board here is that whichever category of enumeration we define, the enumeration type will become a first class type in Swift. This means that it is a type that compiler can check and one that has equal standing to the inbuilt Swift types such as Int , Float , Double or String .

In the next few sections, we’re going to look at each of these enumeration categories. Let’s start with basic enumerations .

Basic Enumerations

In Swift, Basic enumerations are, as you might have guessed, the simplest type of enumeration we can define. In practice, basic enumerations are similar to the enumerations that you may have experience of in C or Objective-C.

Defining an Enumeration

To introduce the definition of a new enumeration type in Swift we use the enum keyword. This is followed by the name of the enumeration type we want to declare (for example TrafficLightColor ). After the name of the enumeration type we then have a pair of braces that enclose the enumerations cases:

enum TrafficLightColor {   // enumeration cases } 

Before we move on there are a couple of things to notice here though. First, is the naming convention used for the enumeration types.

As I’ve already mentioned, when we define an enumeration type it becomes a type just like any other types in Swift. Being types, enumerations should therefore follow Swift’s naming convention for types.

This means that enumeration type names should following an UpperCamelCase naming convention with the first character of the type name being capitalised and the first letter of every subsequent word also being capitalised.

The second, more subtle point to note is that when naming enumeration types they should also be given singular rather than a plural names (i.e. TrafficLightColor rather than TrafficLightColors ). The main reason for this is to make things easier to read when we reference the enumeration cases contained within the enumeration.

Enumeration Cases

As I mentioned earlier, the different potential values that an enumeration type can have are called enumeration cases .

Within the body of an enumeration type, we can have any number of cases we like, including none.

To introduce a new enumeration case we use the case keyword followed a label for the case we wish to introduce:

enum TrafficLightColor {     case red     case amber     case green } 

We can also define multiple cases on a single line by separating each case label with a comma:

enum TrafficLightColor {     case red, amber, green } 

When it comes to naming enumeration cases, by convention each case should follow a lowerCamelCase naming convention with a lowercase first letter and a capitalised letter for each subsequent word.

Note: This lowerCamelCase naming convention is somewhat new guidance that will be introduced in Swift 3.0 (see the Swift 3 API Guidelines for more detail). There is however, nothing stopping you from using it right now in you’re Swift 2.2 code so it might be a good time to get used to it.

When defining enumeration cases, each case within the enumeration should also be unique. For example we couldn’t define two cases called red within our enumeration as the compiler will complain.

Finally (and probably less obviously) when it comes to defining enumeration cases in Swift the enumeration cases are NOT aliases for other values as they are in C or Objective-C. In Swift enumeration cases also don’t get assigned integer values by default. Instead, enumeration cases are full-fledged values in their own right, with each case of the enumeration being defined to be a value of the same type as the enumeration to which they belong.

This is not to say that enumeration cases can’t be assigned a value though. As we’ll see shortly, this can be achieve through either raw values or through the more advanced associated values .

Ok, so we now know how to define basic enumerations but how do we actually use them?

Let’s look at that next.

Assigning Enumeration Cases To Variables and Constants

As I’ve mentioned several times now (I know, I know, broken record), by defining an enumeration in Swift, we are defining a brand new type. This means that we can therefore define variables or constants to hold values of that type:

let firstColor : TrafficLightColor 

Once we have declared the variable or constant of the enumeration type we can then assign an enumeration value to that variable or constant. We do this by specifying the name of the enumeration type followed by a period and the name of the specific enumeration case we wish to assign:

firstColor = TrafficLightColor.red 

Swift’s type inference mechanism can also help us here. Swift’s type inference mechanism works just as well with our own enumeration types as it does with the inbuilt types. For example, in the following code, Swift automatically infers the otherLightColor variable to be of type TrafficLightColor :

var secondColor = TrafficLightColor.green 

Good news but even though type inference can save us a bit of typing there is also an even shorter syntax we can use when assigning enumeration values.

If the type of a variable or constant is already know and that type is an enumeration type, we can assign a new value to a variable (or initialise a constant) by omitting the enumeration type name completely and using just a period followed by the enumeration case we want to assign:

secondColor = .amber 

This works because the Swift compiler is able to infer and check that we are assigning an enumeration value of the correct type and is a short-hand way of referring to the enumeration cases. This very same mechanism also works in calls to functions and closures as well.

Ok, so that’s creating and assigning enumeration values. Let’s now look at the second category of enumeration in Swift – enumerations with raw values.

Raw Value Enumerations

Raw Value Enumeration Basics

So, I’ve already mentioned that enumeration cases in Swift are not assigned values by default as they would be in say C or Objective-C. The thing is, in Swift, assigning values to enumeration cases is a conscious decision. If we choose to, we can pre-populate enumeration cases with default values using something called raw values .

You can think of raw values almost like constants that are assigned to each enumeration case. They are pre-populated values that are assigned when the enumeration is first defined and remain fixed for the life of that enumeration type.

The only caveat with assigning raw values to enumeration cases is that if we choose to assign a raw value to one enumeration case, all the cases in the enumeration must also be assigned a value. It’s an all or nothing thing.

On top of this, the raw values assigned to each of the enumeration cases must also all be of the same type and like the cases themselves must also be individually be unique within the enumeration (i.e. you can’t have two cases with the same raw value).

I know, a lot of rules so let’s have a look at an example. Here is an enumeration for the days of the week:

enum DayOfTheWeek :Int {     case monday = 1     case tuesday = 2     case wednesday = 3     case thursday = 4     case friday = 5     case saturday = 6     case sunday = 7 } 

Most of the declaration above should be relatively familiar. The first difference though is the addition of the colon and the Int type after the enumeration name. This is where we define the type of raw value that each enumeration case will be assigned.

By including the raw value type, we are not only telling the Swift compiler that each and every enumeration case within the enumeration will be assigned a raw value but also the type of value that will be assigned (which in this example will be Int values).

Having specified that each case will have a raw value, the next thing to do is to assign the raw values to the enumeration cases.

To do this, we use the assignment operator ( = ) along with the value we want to assign.

Essentially, that’s it. That’s all there is to assigning raw values to enumeration cases.

So as you can see, in this form Swift enumerations are pretty close to the C enumerations that you may be used to. However, Swift enumerations don’t stop there and there are a few differences that we should talk about. First up is the types of value we can use as raw values.

In C and Objective-C the values associated with enumeration cases are generally limited to just integer values. In Swift though, this is not the case. Swift allows us to assign not only integer values but also Float , Double , Character and even String values as raw values:

enum Emoji :Character {     case umbrella = "/u{2602}"     case hotBeverage = "/u{2615}"     case grinningFace = "/u{1F600}" }   enum Color :String {     case red = "Red"     case amber = "Amber"     case green = "Green" }   enum Constants :Double {     case π = 3.14159265359 // Pi     case φ = 1.61803398874 // Golden Ratio     case √2 = 1.4142135623 // Pythagoras Constant } 

This can be particularly useful, especially if the enumeration cases relate to concepts that are better expressed as a non-integer types.

Implicit Raw Value Assignment

So far so good. Everything seems pretty straight forward but it is a bit of a pain having to explicitly assign values to each and every enumeration case, especially if there are lots of them. The good news is that Swift can help us out here with something called implicit raw value assignment .

Implicit raw value assignment is a mechanism built on top of Swift’s type inference system.

In the case of an enumeration where the raw values are either Int or String values (it doesn’t work for any of the other types such as Float or Double ), Swift can automatically assign raw values to enumeration cases without us having to explicitly assign them.

Take enumerations with raw values of type Int . The general rules here is that unless otherwise stated, Swift will start at zero and assign raw values to each enumeration case with each case being assigned a raw value one greater than that of the previous case.

This does not mean that we can’t explicitly assign raw values of our own though. If Swift finds an explicitly assigned value, it will, continues it’s explicit assignment with the next non-explicit case, assigning it a value of one more than the previous case.

For example we can see this in the example below with the .fri case:

enum ShortDayOfWeek:Int {     mon, tue, wed, thu, fri = 10, sat, sun }  // mon = 0, tue = 1, ..., thu = 3, fri = 10, sat = 11, sun = 12 

Swift continues to assign values for all subsequent cases that don’t have an explicit raw value assigned. In this case it means that the next enumeration case ( .sat ), will be automatically assigned a value of one more than the previous case. This results in the case .sat being assigned a raw value of 11 and so on.

Now this implicit raw value assignment mechanism isn’t just constrained to integer raw values. In Swift, the same mechanism also works with enumerations whose raw values are of type String .

In the case of raw values that are of type String the implicit raw value assigned is, unless otherwise stated, a String representation of the cases label:

enum DayOfWeek:String {     case monday, tuesday, wednesday = "Hello", thursday, friday, saturday, sunday } // monday = "monday", tuesday = "tuesday", wednesday = "Hello", ... , sunday = "sunday" 

Notice that as with integer raw values, we can still explicitly assign individual raw values just as I’ve done with the .wednesday case above.

Enumeration Cases Versus Raw Values

So we now know how to assign raw values to enumeration cases but there is a key point that I want to talk about before we move on and that is that although they may look the same, enumeration cases and raw values are NOT the same thing.

As I mentioned earlier, in Swift, enumeration cases are not aliases for their corresponding raw values as they would be in a language like C. In Swift, the raw value and the enumeration case are two separate things and cannot be used interchangeably. For example, we can’t assign an enumeration case with a raw value of a given type to a variable of that given type:

// Can't assign enum case (of type DayOfTheWeek) to `String` let day : String = DayOfTheWeek.monday // won't compile 

Similarly, we can’t assign a value of the same type as the enumerations raw values to a variable or constant that is defined to hold a value of the enumerations type:

// Can't assign `String` to constant of enum type (DayOfTheWeek) let day : DayOfTheWeek = "monday" // won't compile 

It’s an important point to remember but does beg the question if the enumeration case and the raw value are separate things, how do we access the raw value assigned to an enumeration case and is there a way of accessing the enumeration value from a raw value?

Accessing the Raw Value of an Enumeration Case

First let’s look at the question of how do we access the raw value associated with a given enumeration case?

The good news here is that this is super-simple. When we assign raw values to enumeration cases, each enumeration case automatically gains a read-only property called rawValue which we can use to access the raw value:

let day : String = DayOfWeek.monday.rawValue // day is now "monday" 

So far so good. But what about the other way around – accessing an enumeration case using a raw value?

Enumeration Initialisation Using Raw Values

This way around, things are a little more complicated.

Under the hood, each time we define an enumeration type with raw values, the enumeration type automatically gains an initialisation method that has a single parameter (called rawValue ) of the same type as the enumerations raw value type.

We can use this initialisation method to construct new enumeration values from a given raw value:

let day = DayOfWeek("monday") 

Sounds great but there’s a catch. Not ever raw value will match an enumeration case.

Think about it. Our DaysOfTheWeek example only has 7 cases but has a rawValue parameter of type of String . This means that we could supply any String value to this initialisation method but the vast majority of strings we could supply wouldn’t match cases within our enumeration. So what do we do?

Well, it won’t surprise you then to find out that the enumerations initialisation function is a failable initialiser.

We’ve not touched on failable initialisers yet but all this means is that it instead of returning a value of our enumeration type, it returns an optional value of the enumeration type. If the supplied raw value matches an enumeration case the matching case is returned otherwise the initialiser returns nil .

We can see this at work in the example below:

let day2 : DayOfWeek? = DayOfWeek(rawValue: "Tuesday") // day is now an optional with a value of .Tuesday   let badDay : DayOfWeek? = DayOfWeek(rawValue: "Grrrr") // badDay is now nil 

Ok, let’s leave enumerations with raw values and move on to look at the third of our enumeration types – enumerations with associated values .

Associated Value Enumerations

Defining Enumeration Types with Associated Values

In the last section we looked at how we could use raw values to assign a value to each enumeration type and as we saw, this works well if we want to assign a value to each and every case in the enumeration and can want all those values be of the same type. But in some situations this isn’t ideal.

What if we wanted to assign different types of value to each case? And what if we wanted to assign values to only some of the cases? This is where associated values come in.

Associated values, allow us to combine a set of enumeration cases and yet associate different sets of values of with each enumeration case. This includes any number of values of any type we like as well as potentially having no associated values at all.

It’s worth pointing out though that associated values are a different and mutually exclusive mechanism to the raw values we saw earlier. In Swift we can’t use both raw values and associated values within the same enumeration at the same time. But with that said, associated values are still extremely useful.

Let’s have a look at an example.

Here I’ve defined an enumeration to represent colours in two different colour spaces):

enum ColorSpace {     case rgba(red: UInt8, green: UInt8, blue: UInt8, alpha: Float)     case cmyk(cyan: Float, magenta: Float, yellow: Float, black: Float) } 

Not too daunting, but there are a few things to take note of.

First, there is no raw value type after the enumeration as we saw with raw values (i.e. no colon followed by a type name after the enumeration name). This is because with associated values, each enumeration case can have a different set of associated values and it therefore doesn’t really make sense to specify a single type.

Secondly, notice that the .rgba and .cmyk cases have a different sets of values associated with them with each set of associated values being defined as a comma separated list of label / type pairs between a pair of parentheses. The key point is that each enumeration case only has the associated values that it needs.

Another thing of note is that it is not actually a requirement that we supply labels for each of the associated values. We could just as easily have written the enumeration above as:

enum ColorSpace {     case rgba(UInt8, UInt8, UInt8, Float)     case cmyk(Float, Float, Float, Float) } 

Without the labels though it does make it a little more difficult to understand what is going on, so generally I’d include them.

Finally, notice that the definition of an enumeration with associated values does not actually provide the values as part of the definition itself. This is a little different to what we saw with raw values but does allow us to store any values we like along with the enumeration case and also let’s us change those values each time we create a new enumeration value. To do that though we need to look at how to create enum values with associated values. Let’s do that next.

Assigning Enumerations Values with Associated Values

To create an enumeration value with associated values, we must supply values for each of the associated values of the enumeration at the point we assign the enumeration value:

var color1 = Color.rbga(red: 100, green: 100, blue: 100, alpha:1.0) var color2 = Color.cmyk(cyan: 0.5, magenta: 0.5, yellow: 0.5, black: 0.5) 

The assigned associated values are then stored along with the enumeration value itself.

This means that if we assign a new enumeration value to the variable we lose the previous set of associated values:

var color3 = Color.rgba(red: 100, green: 150, blue: 100, alpha: 0.5) color3 = Color.cmyk(cyan: 0.5, magenta: 0.5, yellow: 0.5, black: 0.5) // associated values of the .RGBA case have been lost. 

It’s that simple. So we now know how to create and assign an enumeration values with associated values, but once assigned how do we actually access those associated values? Enter pattern matching .

Enumerations and Pattern Matching

As you may member from myprevious post, there are a couple of different patterns that we can use with enumerations.

For simple enumerations and enumerations with raw values, we can use a simple enumeration case pattern to match individual enumeration cases:

enum DayOfWeek {     case Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday }   let day = DayOfWeek.Monday   switch day {     case .Monday: print("The Moon's day")     case .Tuesday: print("The Norse god Tyr")     case .Wednesday: print("The Norse god Odin")     case .Thursday: print("The Norse god Thor")     case .Friday: print("The Norse god Frigg")     case .Saturday: print("Saturn's Day")     case .Sunday: print("The Sun's Day")  } // prints "The Moon's day" 

Remember though, that if you do use a switch statement, the statement must be exhaustive and must consider all cases of the enumeration.

In cases where it is not appropriate to provide a specific case within the switch statement for every enumeration case, we can also use the default case as a catch-all:

switch day { case .Saturday, .Sunday:     print("Weekend") default:     print("Weekday") } // prints "Weekday" 

When it comes to enumeration cases with associated values, we also have the option of using the value-binding pattern to match and extract enumeration cases and their associated values:

enum Day {     case Monday(units: Int)     case Tuesday(units: Int)     case Wednesday(units: Int)     case Thursday(units: Int)     case Friday(units: Int)     case Saturday(units: Int)     case Sunday(units: Int) }   let sales = Day.Monday(units: 42)   switch sales { case let .Monday(units):     print("Sold /(units) on Monday") case let .Tuesday(units):     print("Sold /(units) on Tuesday") // ... default:     print("Not sure about the rest of the week!") } 

If we don’t want a full-blown switch statement we can also use the case let or case var syntax to extract the associated value(s) of just a single enumeration case:

var oneDayPromotion = Day.Tuesday(units: 10) if case let Day.Tuesday(units) = oneDayPromotion {     print("Sold /(units) units on Tuesday") } // prints "Sold 10 units on Tuesday" 

Or even extend that idea to match only if it is both the correct case and the correct associated value match:

oneDayPromotion = .Wednesday(units: 50) if case Day.Tuesday(50) = oneDayPromotion {     print("Bonus!! Sold exactly 50 units.") } 

These pattern matching mechanisms will also become even more powerful with the arrival of Swift 3.0. In Swift 3.0, we will be able to bind values from enumeration cases with multiple patterns in a single switch case.

For example here, we have an enumeration with two cases, both with associated values, one Int and one Float but in different orders.

Within the first case of the switch statement we combine multiple patterns and bind the integer associated values from each of the two enumeration cases to a new integer value x :

enum SomeEnum {     case firstCase(Int, Float)     case secondCase(Float, Int) }   let value = SomeEnum.firstCase(20, 1.0)   switch value {     case let .firstCase(x, 1.0), let secondCase(1.0, x):         print(x)     default:         break } // prints 20 

We’re also going to be able to do this within an if-case-let or if-case-var statements as well:

if case let .firstCase(_, y), let .secondCase(y, _) = value {   // Do something with the Float y } 

In all, the following types of pattern will be allowed in Swift 3.0:

case .firstCase(let x, _), .secondCase(_, let y): case let .firstCase(x, _), let .secondCase(_, x):   case let .firstCase(x, y), let .secondCase(y, x):   case let .firstCase(x), let .secondCase(x): 

If you’re interested you can find more detail and the thinking behind this in Swift Evolution Proposal 0043 . Also, whilst I remember if you’re interested in more detail about pattern matching in general, I’d suggest you read my pattern matching article which will hopefully help.

Ok, that’s pattern matching, let’s next look at how we compare to enumeration values for equality.

Enumeration Equality

Checking Equality in Simple and Raw-Value Enumerations

In addition to being able to matching enumeration values using pattern matching, it is not uncommon to also want to use enumeration values within simple boolean equality checks.

With simple enumerations checking for equality is relatively straight forward:

enum Planet {     case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto }   var planet = Planet.Mercury if planet == .Earth {     print("The Blue Marble") } 

This also works for enumerations with raw values:

enum Planet :Int {     case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto }   var planet = Saturn if planet == .Saturn {     print("The Ringed Planet") } 

But things come a little more tricky when we try to compare enumeration cases that have associated values.

Boolean Comparison of Associated Values Enumerations

In the case of enumerations with associated values, Swift is unable to do equality checking automatically.

This is primarily because Swift doesn’t know how to compare the different associated values for each enumeration case to determine whether two cases are equal or not. To fix this, we have to give Swift a bit of help. This means implementing the equality operator ( == ) for the enumeration type ourselves.

If you’ve not implemented your own custom operator this might sound daunting but it’s not actually as complicated as it might sound.

Operators in Swift are really just functions in disguise and the equality operator is no exception. The equality operator is actually a function that takes two values of the same type (traditionally known as the left hand side and right hand side) and returns a Bool to indicate whether the two values were equal or not.

Given this it’s not that hard to implement an equality operator for our example:

enum UserAction {     case Start     case Pause     case Stop     case Restart(delay: Int) }   func ==(lhs: UserAction, rhs: UserAction) -> Bool {     switch (lhs, rhs) {     case let (.Restart(delay1), .Restart(delay2)):         return delay1 == delay2     case (.Start, .Start), (.Pause, .Pause), (.Stop, .Stop):         return true     default:         return false     } } 

For those cases without associated values equality checking is easy. In the case of the .Restart case though, we not only have to ensure the cases match but we also check that the associated values match as well.

With the operator defined, we can then use our new operator to perform our boolean comparisons:

UserAction.Start == UserAction.Start // true UserAction.Start == UserAction.Restart(delay:10) // false UserAction.Restart(delay: 10) == UserAction.Restart(delay:12) // false UserAction.Restart(delay: 10) == UserAction.Restart(delay: 10) // true 

Ok, let’s move on. The next thing we’re going to look at is recursive enumerations

Enumeration Containment

Recursive Enumerations

Introduced in Swift 2.0, recursive enumerations are those enumerations that have another instance of the enumeration as the associated value of one or more of the enumerations cases – say that three times fast!

It’s probably easier to explain recursive enumerations with an example.

Imagine we wanted to represent abinary tree within our code.

We could model the binary tree using an enumeration as follows:

enum BinaryTree<T> {     case leaf(T)     case node(leftChild: BinaryTree, rightChild: BinaryTree?) } 

Don’t get distracted by the generic type T . This is just so we can store an associated value of that type. We’ll look a bit more at enumerations and generics later in the article.

The key point here is that the second case of the enumeration has up to two associated values which themselves are also of the same type as enumeration type that is being defined ( BinaryTree<T> ). It’s a bit mind bending I know.

The other thing with this example is that, as written, it doesn’t actually compile.

If we put this into a playground, Xcode displays the following error:

Recursiveenum ‘BinaryTree<T>’ is not markedas ‘indirect’. 

Due to the way that Swift stores associated values by default, recursive definitions like the one written above aren’t actually allowed.

Instead, we have to tell Swift to modify the way that it stores the associated values of the enumeration by including the indirect keyword. This tells Swift to store the associated values indirectly (essentially by reference) breaking the recursive nature of the definition.

When it comes to including the indirect keyword in the enumeration definition we’ve got two options.

First we can include it before just those enumeration cases that contain the recursive definition:

enum BinaryTree<T> {     case leaf(T)     indirectcase node(left: BinaryTree, right: BinaryTree?) } 

This modifies the storage of just those cases only.

Alternatively, we can include the indirect keyword before the definition of the enumeration as a whole.

Used in this way, Swift will modify the storage of all cases with associated values:

indirect enum BinaryTree<T> {     case leaf(T)     case node(left: BinaryTree, right: BinaryTree?) } 

Nested Enumerations

In addition to being able to define recursive enumerations, Swift also supports nested enumerations .

Nested enumerations are enumerations that are defined within the body of another enumeration.

The main reason for defining nested enumerations is so we can group together enumerations that are related, for example where one enumeration is used as the associated value of one or more cases in another enumeration. In doing so, we can define a hierarchy of related types.

For example say I wanted to represent a race car tyre. We could define both an enumeration to represent the different types of tyre as well as an enumeration to represent the different tyre colours (yeah I was watching Formula 1 when thinking up this example):

enum TyreType {     enum TyreColor {         case purple // UltraSoft         case red // SuperSoft               case yellow // Soft         case white // Medium         case orange // Hard         case green // Intermediate         case blue // FullWet     }          case ultraSoft(color: TyreColor)     case superSoft(color: TyreColor)     case soft(color: TyreColor)     case medium(color: TyreColor)     case hard(color: TyreColor)     case intermediate(color: TyreColor, litresClearedPerSecond: UInt8)     case fullWet(color: TyreColor, litresClearedPerSecond: UInt8) }   let qualificationTyre = TyreType.ultraSoft(color: .purple) let raceTyre = TyreType.intermediate(color:.green, litresClearedPerSecond: 25) 

To access embedded enumeration types we use dot syntax. For example, the full type of the TyreColor type is actually TyreType.TyreColor :

let color : TyreType.TyreColor = .white 

Note: The example above is not the best as there is no direct encoding of the relationship between the type type and tyre colour. As we’ll see shortly, there are better ways to do this where we can encode this relationship between the two enumerations directly within the enumerations themselves.

Contained Enumerations

If nesting enumerations doesn’t fit you’re needs, we can also contain enumerations within other structured types such as classes or structs. Again, this helps to group related information together:

struct F1RacingCar {     enum RaceTeam {         case mercedes         case ferrari         case redBullRacing         case williams         case toroRosso         case mcLaren         case forceIndia         case renault         case sauber         case haasF1         case manorRacing     }       enum TyreType {         case ultraSoft         case superSoft         case soft         case medium         case hard         case intermediate         case fullWet     }       let team: RaceTeam     let tyre: TyreType }   let racingCar = F1RacingCar(team: .mercedes, tyre: .superSoft) 

Enumeration Methods and Properties

Ok. So far we’ve covered most of the basics of enumerations in Swift and we’ve seen how we can use them to group sets of related values together and store additional data along with those values. For this most part, if you’ve used enumerations in other languages, this won’t have been too earth shattering.

However, enumerations in Swift don’t stop there. In addition to the features we’ve already looked at, enumerations in Swift also support a number of other features that you’d normally only expect to see on structs and classes. The first of these features is the ability to support properties .

Properties are values that are associated with a particular class, structure or enumeration. Don’t confuse them with the raw values or associated values we’ve looked at though. As we’ll see, properties are a different concept.

In Swift, there are types of property – stored properties and computed properties . Stored properties are constants or variables that are stored as part of the class or struct. Computed properties don’t actually store a value. Instead they provide a getter and an optional setter that can be used to both retrieve and set other properties and values indirectly.

Enumeration Instance Properties

Let’s look at another example and revisit our race tyre example from earlier. Instead of storing the color as a string with each enumeration case, we could add a computed property with a getter that would, based on the current enumeration value, return the correct tyre color:

enum Tyre {     enum TyreColor {         case purple // UltraSoft         case red // SuperSoft         case yellow // Soft         case white // Medium         case orange // Hard         case green // Intermediate         case blue // FullWet     }       case ultraSoft     case superSoft     case soft     case medium     case hard     case intermediate(litresClearedPerSecond: UInt8)     case fullWet(litresClearedPerSecond: UInt8)       var color :TyreColor {         get {             switch self {             case .ultraSoft: return .purple             case .superSoft: return .red             case .soft: return .yellow             case .medium: return .white             case .hard: return .orange             case .intermediate: return .green             case .fullWet: return .blue             }         }     } }   let qualificationTyre = Tyre.ultraSoft.tyreColor // .purple let raceTyre = Tyre.intermediate(litresClearedPerSecond: 25).tyreColor // .green 

Note: As the color property is a read-only computed property I could have also written it in a slightly more compact form where I omitted the get keyword but the meaning is the same. I’ve also had to declare the property as a variable rather than a constant as all computed properties are variables in Swift.

The main area of interest with this example is the use of the keyword self .

When used within an instance property the keyword self is used to refer to the current enumeration value. So if the property was accessed on an enumeration value of .fullWet then self would refer to a value of .fullWet and if the enumeration value were .hard then it would refer to .hard . Get the idea?

Within he body of the getter, we can then use self to perform different actions based on the current enumeration value as I’ve done here with the switch statement.

Now, in addition to being a computed property, the tyreColor property above is also an example of something called an instance property .

Instance properties, as their name suggests, are properties associated with a particular instance of a given type. To access instance properties on an enumeration value, we combine the enumeration type, case and property we want to access using dot syntax:

Tyre.ultraSoft.tyreColor // .purple 

Enumeration Type Properties

Ok, let’s talk about type properties .

In addition to the computed instance properties we just looked at enumerations also support type properties .

Type properties are properties that are associated with the type itself rather than a particular instance of the type. Where instance properties on enumerations are restricted to only computed properties, type properties on enumerations can be either computed or stored properties.

To indicate that a property is a type property (rather than an instance property) we prefix the property declaration with the static keyword. For example:

enum Planet {     case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto       static let speedOfLightMsPerSec = 299792458 // Stored Type Property     static var speedOfLightKmPerSec :Int { // Computed Type Property         return self.speedOfLightMsPerSec / 1000     } } 

Remember that computed properties can’t be defined as constants in Swift though so in this example the speedOfLightKmPerSec property is defined as a variable rather than a constant.

To access a type property we access the property on the type (rather than an instance of that type) again using dot syntax:

print(Planet.speedOfLightMsPerSec) // prints 299792458 

And if we use the keyword self within the type property, self refers to the type itself rather than the current enumeration value.

Ok, enough properties. Let’s look at methods .

Enumeration Instance Methods

Given our discussion of properties, it probably won’t surprise you that enumerations in Swift also support methods as well.

Enumeration methods allow us to attach functionality to enumerations without having to resort to free functions and as with properties, enumeration methods come in two forms – instance methods and type methods . Let’s look at instance methods first.

To define an instance method on an enumeration type we define the method just like we would with any other method except this time we do so within the enumeration itself:

enum Planet {     case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto       func isAFormalPlanet() -> Bool {         switch self {         case Pluto: return false         default: return true         }     } } 

Just like we saw with instance properties, instance methods can access the current enumeration value using the self keyword.

With the instance method defined, we then use dot syntax to call the method on the enumeration value:

let pluto = Planet.pluto.isAFormalPlanet() // pluto = false let earth = Planet.earth.isAFormalPlanet() // earth = true 

Mutating Enumeration Instance Methods

Now, in addition to the simple instance methods we just looked at we can also define mutating instance methods as well.

Mutating instance methods allow us to modify or mutate the current enumeration value. We do this by assigning a new value to self within the body of the method. However, in addition to assigning a new value to self , we also have to mark the method as mutating :

enum Planet :Int {     case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto       mutating func next() {         if self == .pluto {             self = Planet(rawValue: 1)!         } else {             self = Planet(rawValue: self.rawValue + 1)!         }     } }   var planet = Planet.pluto planet.next()  // planet is now equal to .mercury planet.next()  // planet is now equal to .venus 

Enumeration Type Methods

In addition to instance methods, enumerations in Swift also support type methods . Where instance methods are called on instances of the enumeration type, type methods unsurprisingly are called on the type itself. To indicate that a method is a type method (rather than an instance method), we prefix the func keyword of the method declaration the with the keyword static :

enum Planet :Int {     case Mercury = 1, Venus, Earth, Mars, Jupiter,          Saturn, Uranus, Neptune, Pluto       static let orbitsInAU : [Planet : Float] = [         Mercury : 0.38, Venus : 0.72, Earth: 1.00,         Mars : 1.52, Jupiter : 5.20, Saturn: 9.54,         Uranus: 19.22, Neptune: 30.06, Pluto: 39.50]       static func orbitForPlanetInAU(planet : Planet) -> Float? {         if let orbit = orbitsInAU[planet] {             printOrbit(planet, orbitAU: orbit)             return orbit         } else {             return nil         }     }       static func printOrbit(planet: Planet, orbitAU : Float) {         print("The orbit for /(planet) is " +              "/(orbitAU) astronomical units.")     } } 

And to call a type method on an enumeration we again use dot syntax, this time on the type:

let orbit = Planet.orbitForPlanetInAU(.Saturn) // prints "The orbit for Saturn is 9.54 astronimical units." // orbit is set to 9.54 

And as we saw with type properties if we choose to use the self keyword within the body of a type method, self refers to the type itself rather than the current enumeration value.

Custom Enumeration Initialisers

As we talked about with raw values earlier, by default raw value enumerations in swift receive an initialisation method that allows us to create an enumeration value from a raw value:

enum Planet :Int {     case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto }   let earth : Planet? = Planet(rawValue: 3) // .earth 

Beyond this though, we can also write our own custom initialisers should we need them. Say for example, that we wanted to initialise our Planet enumeration using a String value. We could write:

enum Planet :Int {     case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto       init?(name : String) {         switch name {         case "Mercury": self = .mercury         case "Venus": self = .venus         case "Earth": self = .earth         case "Mars": self = .mars         case "Jupiter": self = .jupiter         case "Saturn": self = .saturn         case "Uranus": self = .uranus         case "Neptune": self = .neptune         case "Pluto": self = .pluto         default: return nil         }     } } Planet("Earth") // .earth Planet("") // nil 

In this example, I’ve used a failable initialiser returning nil if the string doesn’t match or the enumeration value should a match occur. We can also write non-failable initialisers as well:

enum Planet :Int {     case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto       init() {         self = .earth     } } 

Side Note – Referring To Enumeration Cases

Before we move on, I just wanted to take a moment to talk about the how we refer to enumeration cases.

As we’ve seen, when referring to an enumeration case from outside of an enumeration we have to prefix our enumeration cases with a period.

In Swift 2.2, when referring to individual enumeration cases from within the enumeration itself we can refer to those cases either with, or without, the prefixed period though. Semantically they’re exactly the same.

For example the following is currently valid in Swift 2.2 (note the mixed use of prefixed periods in the body of the color getter):

enum Tyre {     enum TyreColor {         case purple // UltraSoft         case red // SuperSoft         case yellow // Soft         case white // Medium         case orange // Hard         case green // Intermediate         case blue // FullWet     }       case ultraSoft     case superSoft     case soft     case medium     case hard     case intermediate(litresClearedPerSecond: UInt8)     case fullWet(litresClearedPerSecond: UInt8)       var color :TyreColor {         get {             switch self {             case ultraSoft: return .purple             case .superSoft: return .red             case soft: return .yellow             case .medium: return .white             case hard: return .orange             case .intermediate: return .green             case fullWet: return .blue             }         }     } } 

This will however be changing in Swift 3.0. Due to the acceptance of proposal SE-0036 things will become a little more consistent and the inclusion of the periods before the enumeration cases will become mandatory both inside and outside of the enumeration. With this in mind I’d try to get into the habit of using them now if you can.

Enumerations And Protocols

Enumerations and Basic Protocol Conformance

Due to the fact that enumerations in Swift support methods, we can also make enumerations conform to protocols.

As you’re may be aware, a protocol defines an interface or contract that other structures such as enumerations, structs and classes can conform to.

Just as we would with a struct or a class, we can declare that an enumeration will conform to a protocol by including the protocol name after the name of the enumeration type. If the enumeration type also has raw values, the raw value comes first followed by a comma and the protocol(s) to which the enumeration type will conform.

Let’s have a look at a simple example.

The CustomStringConvertable protocol is a simple protocol with only one requirement – that any type conforming to that protocol has a read only description property of type String :

protocol CustomStringConvertable {     var description:String { get } } 

If we wanted to make our Planet enumeration conform to that protocol, we could write:

enum Planet : Int, CustomStringConvertable {     case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto       var description:String {         switch self {         case .Mercury: return "Mercury"         case .Venus: return "Venus"         case .Earth: return "Earth"         case .Mars: return "Mars"         case .Jupiter: return "Jupiter"         case .Saturn: return "Saturn"         case .Uranus: return "Uranus"         case .Neptune: return "Neptune"         case .Pluto: return "Pluto"         }     } } 

Notice the inclusion of both a raw value (of type Int ) and the declaration of conformance to the CustomStringConvertable protocol. If the enumeration had no raw values, we would simply list the protocol after the colon.

So that was a pretty simple example, let’s look at a more complex one.

Enumerations and Advanced Protocol Conformance

Some protocol implementations may need internal state handling to cope with the protocols requirements. For example, say we had a protocol Lifespan that allowed us to manage the number of lives a player had left in a game:

protocol Lifespan {     var livesRemaining:Int { get }     mutating func addLife()     mutating func removeLife() } 

Normally you might think this would require a stored property to store the number of lives that the character had remaining. Enumerations in Swift, don’t support stored instance properties though. We can however, substituted associated values instead:

enum PlayerState :Lifespan {     case dead     case alive(livesRemaining: Int)       var livesRemaining :Int {         switch self {         case .dead : return 0         case .alive(let livesRemaining): return livesRemaining         }     }       mutating func addLife() {         switch self {         case .dead:             self = .alive(livesRemaining: 1)         case .alive(let livesRemaining):             self = .alive(livesRemaining: livesRemaining + 1)         }     }       mutating func removeLife() {         switch self {         case .alive(let livesRemaining):             if livesRemaining == 1 {                 self = .dead             } else {                 self = .alive(livesRemaining: livesRemaining - 1)             }         case .dead:             break         }     } } 

Notice here that although we’ve provided a livesRemaining property in order to conform to the Lifespan protocol, the property is actually a proxy for the associated value on the .alive case. It’s actually a nice technique.

To check everything works, we can also try it out:

var state = PlayerState.dead // .dead state.addLife() // .alive(1) state.livesRemaining // 1 state.addLife() // .alive(2) state.livesRemaining // 2   state.removeLife() // .alive(1) state.livesRemaining // 1   state.removeLife() // .dead state.livesRemaining // 0   state.removeLife() // .dead state.livesRemaining // 0 

Ok, two more things I want to touch on and then we’re done. The first of these is using enumerations with extensions.

Using Enumerations with Extensions

I’ve not really covered extensions before and it’s a topic for a future article so I’m not going to go into it in depth here. With that said, extensions in Swift are a way of adding new functionality to an existing class, structure, enumeration or protocol type. One of extensions big benefits is that we can also do this even if we don’t have the original source code for the entity we’re extending. This makes them pretty powerful.

Via extensions we can add computed instance and type properties, can define instance and type methods, can add new initialisers, can define new nested types and can make an existing type conform to a protocols – all the sorts of things we’ve looked at with enumerations!

We can also use extensions to extend protocols (called protocol extensions ) to provide implementations of the protocols requirements and / or add functionality within the protocol itself. I’m not going to cover it here but it’s a powerful technique.

Anyway, enumerations work extremely well with extensions.

To introduce a new extension we use the extension keyword. This is followed by the name of the type we wish to extend.

Additionally, we can also then include a colon and a comma separated list of any additional protocols we want to the type to conform to. Within the body of the extension, we then provide the additional properties and methods for the additional features we wish to add.

For example, we could re-write our game example above using an extension:

enum PlayerState {     case dead     case alive(livesRemaining: Int) }   extension PlayerState :Lifespan {       enum Error :ErrorType {         case PlayerIsDead     }       var livesRemaining :Int {         switch self {         case .dead : return 0         case .alive(let livesRemaining): return livesRemaining         }     }       mutating func addLife() {         switch self {         case .dead:             self = .alive(livesRemaining: 1)         case .alive(let livesRemaining):             self = .alive(livesRemaining: livesRemaining + 1)         }     }       mutating func removeLife() throws {         switch self {         case .alive(let livesRemaining):             if livesRemaining == 1 {                 self = .dead             } else {                 self = .alive(livesRemaining: livesRemaining - 1)             }         case .dead:             throw Error.PlayerIsDead         }     } } 

When used in this way, extensions are a nice way to group together the additional properties and methods needed for protocol conformance whilst keeping the original declaration of the enumeration clear and concise. No doubt we’ll touch more on extensions and enumerations in a future article.

Using Enumerations with Generics

The final thing I want to close with today is using enumerations with generics.

We’ve seen this a couple of times already in this article so it won’t surprise you when I say that enumerations can also be defined over generic parameters.

When enumerations are combined with generic parameters, we can use the generic parameters to adapt the associated values of enumeration cases or change the types of values returned in the enumerations methods or even change the types of their properties.

To define an enumeration over a generic type we specify the generic type, also known as a type parameter between a set of angled brackets ( <> ) after the enumeration type name. The type parameter is a placeholder for the real type that will be filled in when the generic type is actually used.

For example, the simplest form of generic enumeration, and one that is defined in Swift’s standard library, is the Optional<T> type:

enum Optional<T> {     case None     case Some(T) } 

We’ve seen the optional type in a a number of previous articles. The Optional<T> type, takes a single type parameter which is used to specify the type of associated value stored with the .Some case. By making the optional type generic, it allows us to use the Optional type with any other type like:

let optional1 = Optional<String>.Some("Hello") let optional2 = Optional<Float>.None 

Summary

So there we have it. A grand tour of enumerations in Swift. Congratulations, you’ve made it!

In this article we’ve looked at the basics of enumerations, looked at how to associate raw values with each of the enumeration cases and also looked at how, as an alternative, we can use associated values to provide even greater flexibility. Beyond this we’ve also looked at a number of features that set enumerations in Swift apart from the enumerations that you might experienced in other languages. Features such as their support for properties and methods, their ability to conform to protocols as well as the ability to define generic enumeration.

With all this power and flexibility, enumerations are a key component of writing great Swift code and every day developers are thinking of new and improved ways to use them. Examples include representing status codes, defining API end points, using them in to define errors and also using them as an alternative to magic string values in your code. Which ever use you choose, they are a great tool to have in your Swift toolbox and I hope this article has opened your eyes to just what they make possible.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Swift Enumerations

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址