Swift enumerations are a first-class types in their own right, that means they can adopt many features traditionally supported only by classes. In this chapter we will cover how enumeration can define custom initializers to provide an initial case value, and how they can be extended and conform to protocols.
Remember to read the first chapters (Part 1 and Part 2) if you missed them or you want to refresh the basic concepts of Swift enums.
Protocols
A protocol defines an interface of methods, properties and other requirements that can be adopted by a class, structure or enumeration. Any type that satisfies the requirement of a protocol must provide an implementation of those requirements.
For demonstration purposes we use the LayerActions
enumeration declared in the previous chapter, which defines a set of actions that users can execute in order to modify a layer in a design tool. To execute and describe each action we declare the protocol Taskable
, which defines a method to execute a task and a description property that defines the task to be executed.
protocol Taskable { | |
func execute() | |
var description: String { get } | |
} | |
enum LayerActions: Taskable { | |
case scale(width: Double, height: Double) | |
case rotate(degrees: Double) | |
case fill(hex: String) | |
func execute() { | |
switch self { | |
case .scale(let width, let height): | |
print("Executes scale action") | |
case .rotate(let degrees): | |
print("Executes rotate action") | |
case .fill(let hexColor): | |
print("Executes fill action") | |
} | |
} | |
var description: String { | |
switch self { | |
case .scale(let width, let height): | |
return "Scales the layer to \(width)x\(height)" | |
case .rotate(let degrees): | |
return "Rotates the layer \(degrees) degrees" | |
case .fill(let hexColor): | |
return "Fills the layer with \(hexColor) color" | |
} | |
} | |
} | |
let scale = LayerActions.scale(width: 120, height: 60) | |
let fill = LayerActions.fill(hex: "#FFFFFF") | |
fill.description | |
fill.execute() |
As you can see, the enumeration now conforms to the protocol requirements in the enumeration by implementing the method execute()
and the computed property description
.
Extensions
An extension adds new functionality to an existing class, structure, enumeration or protocol type. You can use an extension to add computed properties, define methods, define initializers, or make the existing type conform to a protocol among others.
In our case, we will extend the LayerActions
enumeration to conform the protocol Taskable
and make it easier to read, understand and maintain the code.
protocol Taskable { | |
func execute() | |
var description: String { get } | |
} | |
enum LayerActions { | |
case scale(width: Double, height: Double) | |
case rotate(degrees: Double) | |
case fill(hex: String) | |
} | |
extension LayerActions: Taskable { | |
func execute() { | |
switch self { | |
case .scale(let width, let height): | |
print("Executes scale action") | |
case .rotate(let degrees): | |
print("Executes rotate action") | |
case .fill(let hexColor): | |
print("Executes fill action") | |
} | |
} | |
var description: String { | |
switch self { | |
case .scale(let width, let height): | |
return "Scales the layer to \(width)x\(height)" | |
case .rotate(let degrees): | |
return "Rotates the layer \(degrees) degrees" | |
case .fill(let hexColor): | |
return "Fills the layer with \(hexColor) color" | |
} | |
} | |
} |
We can add as many extensions as we want, for instance, we can extend the previous enumeration in order to implement a different protocol. Here’s an example that extends the enumeration twice to separate the implementation of protocols Taskable
and Serializable
.
protocol Taskable { | |
func execute() | |
var description: String { get } | |
} | |
protocol Serializable { | |
func serialize() | |
} | |
enum LayerActions { | |
case scale(width: Double, height: Double) | |
case rotate(degrees: Double) | |
case fill(hex: String) | |
} | |
extension LayerActions: Taskable { | |
func execute() { | |
switch self { | |
case .scale(let width, let height): | |
print("Executes scale action") | |
case .rotate(let degrees): | |
print("Executes rotate action") | |
case .fill(let hexColor): | |
print("Executes fill action") | |
} | |
} | |
var description: String { | |
switch self { | |
case .scale(let width, let height): | |
return "Scales the layer to \(width)x\(height)" | |
case .rotate(let degrees): | |
return "Rotates the layer \(degrees) degrees" | |
case .fill(let hexColor): | |
return "Fills the layer with \(hexColor) color" | |
} | |
} | |
} | |
extension LayerActions: Serializable { | |
func serialize() { | |
// Code that serialize the enum into a json file | |
} | |
} |
Initializers
Enumerations with a raw-value type automatically receive an initializer that takes as a parameter a value of the raw value’s type. Here’s an example of an enumeration with a raw-value type that uses the initializer to create a new instance.
enum Polygon: Int { | |
case triangle = 3 | |
case quadrilateral = 4 | |
case pentagon = 5 | |
} | |
let triangle = Polygon(rawValue: 3) |
Initilizers return either an enumeration case or nil
. As you can see in the following example, the variable q
is of type Polygon?
and the value returned by the raw initializer is nil
.
enum Polygon: Int { | |
case triangle = 3 | |
case quadrilateral = 4 | |
case pentagon = 5 | |
} | |
let edges = 2 | |
if let q = Polygon(rawValue: edges) { | |
print("Polygon created with \(edges) edges") | |
} else { | |
print("There isn't a polygon with \(edges) edges") | |
} |
You can also create custom initializers even if the enumeration is not a raw-value typed enumeration.
enum Size { | |
case small, medium, large, extraLarge | |
init?(size: Int) { | |
if (size <= 10) { | |
self = .small | |
} else if (size > 10 && size <= 20) { | |
self = .medium | |
} else if (size > 20 && size <= 30) { | |
self = .large | |
} else { | |
self = .extraLarge | |
} | |
} | |
} | |
enum GamePlatform { | |
case pc, playstation, xbox, nintendo | |
init?(name: String) { | |
if (name == "ps2" || name == "ps3" || name == "ps4") { | |
self = .playstation | |
} else if (name == "xbox" || name == "xbox 360" || name == "xbox one" || name == "xbox one x") { | |
self = .xbox | |
} else if (name == "nes" || name == "snes" || name == "switch") { | |
self = .nintendo | |
} else { | |
self = .pc | |
} | |
} | |
} | |
let nintendo = GamePlatform(name: "snes") | |
let extralarge = Size(size: 40) |
If you find this post helpful, please recommend it for others to read.
Top comments (0)