Getting to Know Enum, Struct and Class Types in Swift
Learn all about enums, structs, and classes in Swift, including value vs reference semantics, dynamic member lookup, and protocol conformance. By Adam Rush.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Getting to Know Enum, Struct and Class Types in Swift
35 mins
- It’s All About Those Types
- Shapes With Scalable Vector Graphics (SVG)
- Getting Started
- Using Enums
- CaseIterable
- Associated Values
- Protocols and Methods With an Enum
- Initializers With an Enum
- Namespaces With Enum
- Taking Stock of Enums
- Using Structs
- Dynamic Member Lookup
- Value vs. Reference Types
- Rectangle Model
- Show Me Some SVG
- Using Classes
- Why Even Use a Class?
- Implementing Computed Properties
- Retroactive Modeling and Type Constraining
- Where to Go From Here?
Associated Values
ColorName
is good for named colors, but you might recall that CSS colors have several representations: named, RGB, HSL and more.
Enums in Swift are great for modeling things that have one of a number of representations, such as CSS color, and each enum case can be paired with its own data. These data are called associated values.
Define CSSColor
using an enum by adding the following to the end of your playground:
enum CSSColor {
case named(name: ColorName)
case rgb(red: UInt8, green: UInt8, blue: UInt8)
}
With this definition, you give the CSSColor
model one of two states:
- It can be
named
, in which case the associated data is aColorName
value. - It can be
rgb
, in which case the associated data is threeUInt8
(0-255) numbers for red, green and blue.
Note that this example leaves out rgba, hsl and hsla cases for brevity.
Protocols and Methods With an Enum
Because CSSColor
has associated values, it’s harder (though not impossible) to make it conform to RawRepresentable
. The easiest way to get a string representation out of the new enum is by making it conform to CustomStringConvertible
.
The key to inter-operating with the Swift Standard Library is to adopt standard library protocols.
Add the following extension for CSSColor
to the end of your playground:
extension CSSColor: CustomStringConvertible {
var description: String {
switch self {
case .named(let colorName):
return colorName.rawValue
case .rgb(let red, let green, let blue):
return String(format: "#%02X%02X%02X", red, green, blue)
}
}
}
In this implementation, description
switches upon self
to determine if the underlying model is a named or RGB type. In each case, you convert the color to the required string format. The named case just returns the string name, whereas the RGB case returns the red, green and blue values in the required format.
To see how this works, add the following to your playground:
let color1 = CSSColor.named(name: .red)
let color2 = CSSColor.rgb(red: 0xAA, green: 0xAA, blue: 0xAA)
// prints color1 = red, color2 = #AAAAAA
print("color1 = \(color1), color2 = \(color2)")
Everything is type checked and proven correct at compile time, unlike when you use only String
values to represent colors.
The extension style is nice because it makes what you define fully explicit, in order to conform to a given protocol. In the case of CustomStringConvertible
, you’re required to implement a getter for description
.
The extension style is nice because it makes what you define fully explicit, in order to conform to a given protocol. In the case of CustomStringConvertible
, you’re required to implement a getter for description
.
Initializers With an Enum
Just like classes and structs in Swift, you can also add custom initializers to enum. For example, you can make a custom initializer for grayscale values.
Add this extension to your playground:
extension CSSColor {
init(gray: UInt8) {
self = .rgb(red: gray, green: gray, blue: gray)
}
}
Add the following to your playground:
let color3 = CSSColor(gray: 0xaa)
print(color3) // prints #AAAAAA
You can now conveniently create grayscale colors!
Namespaces With Enum
Named types can act as a namespace to keep things organized and to minimize complexity. You created ColorName
and CSSColor
, and, yet, ColorName
is only ever used in the context of a CSSColor
.
Wouldn’t it be nice if you could nest ColorName
within the CSSColor
model?
Well, you can! Remove ColorName
from your playground and replace it with the following code:
extension CSSColor {
enum ColorName: String, CaseIterable {
case black, silver, gray, white, maroon, red, purple, fuchsia, green,
lime, olive, yellow, navy, blue, teal, aqua
}
}
This moves ColorName
into an extension on CSSColor
. Now, ColorName
is tucked away, and the inner type is defined on CSSColor
.
Since it’s now nested, the for
loop you created earlier needs to be updated as well. Change it to the following:
for color in CSSColor.ColorName.allCases {
print("I love the color \(color).")
}
However, if you receive an error in your playground about ColorName
being an undeclared type, move the above extension to just below your enum definition of CSSColor
to clear the playground error.
Sometimes, playgrounds are sensitive to the ordering of definitions, even when it doesn’t really matter.
However, if you receive an error in your playground about ColorName
being an undeclared type, move the above extension to just below your enum definition of CSSColor
to clear the playground error.
Sometimes, playgrounds are sensitive to the ordering of definitions, even when it doesn’t really matter.
Taking Stock of Enums
Enums are much more powerful in Swift than they are in other languages, such as C or Objective-C. As you’ve seen, you can extend them, create custom initializer methods, provide namespaces and encapsulate related operations.
So far, you’ve used enum
to model CSS colors. This works well because CSS colors are a well understood, fixed W3C specification.
Enumerations are great for picking items from a list of well-known things, such as days of the week, faces of a coin or states in a state machine. It’s no surprise that Swift optionals are implemented in terms of an enum with a state of .none
or .some
with an associated value.
On the other hand, if you wanted CSSColor
to be user extensible to other color space models that are not defined in the W3C specification, an enumeration is not the most useful way of modeling colors.
That brings you to the next Swift named model type: structures or struct
s.
Using Structs
Because you want your users to be able to define their own custom shapes within the SVG, using an enum
is not a good choice for defining shape types.
You cannot add new enum
cases later in an extension. To enable that behavior, you have to use either a class
or a struct
.
The Swift Standard Library team suggests that, when you create a new model, you should first design the interface using a protocol. You want your shapes to be drawable, so add this to your playground:
protocol Drawable {
func draw(with context: DrawingContext)
}
The protocol defines what it means to be Drawable
. It has a draw method that draws to something called a DrawingContext
.
Speaking of DrawingContext
, it’s just another protocol. Add it to your playground as follows:
protocol DrawingContext {
func draw(_ circle: Circle)
}
A DrawingContext
knows how to draw pure geometric types: Circle, Rectangle and other primitives. Take note of something here: the actual drawing technology is not specified, but you could implement it in terms of anything — SVG, HTML5 Canvas, Core Graphics, OpenGL, Metal, etc.
You’re ready to define a circle that adopts the Drawable
protocol. Add this to your playground:
struct Circle: Drawable {
var strokeWidth = 5
var strokeColor = CSSColor.named(name: .red)
var fillColor = CSSColor.named(name: .yellow)
var center = (x: 80.0, y: 160.0)
var radius = 60.0
// Adopting the Drawable protocol.
func draw(with context: DrawingContext) {
context.draw(self)
}
}
Any type that conforms to DrawingContext
now knows how to draw a Circle
.