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?
Why Even Use a Class?
Given the above downsides, you might wonder why you would ever want to use a class.
For starters, they allow you to adopt mature and battle-tested frameworks like Cocoa and Cocoa Touch.
Additionally, classes do have more important uses. For example, a large memory-hogging, expensive-to-copy object is a good candidate for wrapping in a class. Classes can model an identity well. You may have a situation in which many views are displaying the same object. If that object is modified, all of the views also reflect changes in the model. With a value type, synchronizing updates can become an issue.
In short, classes are helpful anytime reference versus value semantics come into play.
Check out this two-part tutorial on the subject: Reference vs. Value Types in Swift.
Implementing Computed Properties
All named model types let you create custom setters and getters that don’t necessarily correspond to a stored property.
Suppose you want to add a diameter
getter and setter to your Circle
model. It’s easy to implement it in terms of the existing radius
property.
Add the following code to the end of your playground:
extension Circle {
var diameter: Double {
get {
return radius * 2
}
set {
radius = newValue / 2
}
}
}
This implements a new computed property that is purely based on the radius. When you get the diameter, it returns the radius doubled. When you set the diameter, it sets the radius to the value divided by two. Simple!
More often than not, you only want to implement a special getter. In this case, you don’t have to include the get {} keyword block and can just specify the body. Perimeter and area are good use cases for this.
Add the following to the Circle
extension you just added:
// Example of getter-only computed properties
var area: Double {
return radius * radius * Double.pi
}
var perimeter: Double {
return 2 * radius * Double.pi
}
Unlike classes, struct methods are not allowed to modify, or mutate, stored properties by default, but they can if you declare them as mutating.
For example, add the following to the Circle
extension:
func shift(x: Double, y: Double) {
center.x += x
center.y += y
}
This tries to define a shift()
method on circle, which moves the circle in space — i.e., it changes the center point.
But this generates the following error on the two lines, which increment the center.x
and center.y
properties.
// ERROR: Left side of mutating operator has immutable type ‘Double'
This can be fixed by adding the mutating keyword, like so:
mutating func shift(x: Double, y: Double) {
center.x += x
center.y += y
}
This tells Swift that it’s OK that your function mutates the struct.
Retroactive Modeling and Type Constraining
One of the great features of Swift is retroactive modeling. It lets you extend the behavior of a model type even if you don’t have the source code for it.
Here’s a use case: Suppose you’re a user of the SVG code and you want to add an area
and perimeter
to Rectangle
just like Circle
.
To see what this all means, add this to your playground:
extension Rectangle {
var area: Double {
return size.width * size.height
}
var perimeter: Double {
return 2 * (size.width + size.height)
}
}
This adds an extension
to add area
and perimeter
to an existing model, and, now, you’ll formalize these methods into a new protocol.
Add this to your playground:
protocol ClosedShape {
var area: Double { get }
var perimeter: Double { get }
}
That gives you an official protocol.
Next, you’ll tell Circle
and Rectangle
to conform to this protocol retroactively by adding the following to your playground:
extension Circle: ClosedShape {}
extension Rectangle: ClosedShape {}
You can also define a function that, for example, computes the total perimeter of an array of models (any mix of structs, enums or classes) that adopt the ClosedShape
protocol.
Add the following to the end of the playground:
func totalPerimeter(shapes: [ClosedShape]) -> Double {
return shapes.reduce(0) { $0 + $1.perimeter }
}
totalPerimeter(shapes: [circle, rectangle])
This uses reduce
to calculate the sum of perimeters. You can learn more about how it works in An Introduction to Functional Programming.
Where to Go From Here?
In this tutorial, you learned about enum
, struct
and class
— the named model types of Swift.
All three have key similarities: They provide encapsulation, can have initializer methods, can have computed properties, can adopt protocols, and can be modeled retroactively.
I hope you have enjoyed this whirlwind tour of the named model types in Swift. If you’re looking for a challenge, consider building a more complete version of the SVG rendering library. You’re off to a good start!
As always, if you have questions or insights you would like to share, please use the forums below!