Applying Extensions
01 February, 2021
When you modified your Incomplete Puzzle solutions to handle incorrect user input you probably felt like your task was quite... repetitive.
For example, you likely had repeated blocks of code, like this, an excerpt from The Cell Sell puzzle:
// Get daytime minutes
var day = 0
while true {
// Prompt the user
print("Number of daytime minutes? ")
// Collect input
guard let givenInput = readLine() else {
continue
}
// Convert to an integer
guard let givenInteger = Int(givenInput) else {
continue
}
// Check that the value is positive
guard givenInteger >= 0 else {
continue
}
// Assign the given integer to our "day" variable
day = givenInteger
// Stop the loop
break
}
// Get evening minutes
var evening = 0
while true {
// Prompt the user
print("Number of evening minutes? ")
// Collect input
guard let givenInput = readLine() else {
continue
}
// Convert to an integer
guard let givenInteger = Int(givenInput) else {
continue
}
// Check that the value is positive (zero or above)
guard givenInteger >= 0 else {
continue
}
// Assign the given integer to our "evening" variable
evening = givenInteger
// Stop the loop
break
}
Whenever you find yourself using essentially the same logic in multiple places in your code – sometimes called copy and paste syndrome – stop yourself. There is a better way!
Extensions "add new functionality to an existing class, structure, enumeration, or protocol type."
You can even extend types that you did not write yourself!
Let's do that now, with the Int
type.
Create a new file in your Incomplete Puzzles project named Extensions.swift
.
Copy and paste this code into the new file, below the import Foundation
line:
extension Int {
static func collectInput(withPrompt prompt: String, minimum: Int?, maximum: Int?) -> Int {
// Loop until a valid value is provided
while true {
// Prompt the user
print(prompt)
// Collect the input
guard let givenInput = readLine() else {
continue
}
// Convert to an integer
guard let givenInteger = Int(givenInput) else {
continue
}
// If a lowest value for the integer was specified...
if let minimumValue = minimum {
// ... then check that the given integer is greater than or equal to the lowest desired value.
guard givenInteger >= minimumValue else {
continue
}
}
// If an highest possible value for the integer was specified...
if let maximumValue = maximum {
// ... then check that the given integer is less than or equal to the highest desired value.
guard givenInteger <= maximumValue else {
continue
}
}
// If we've made it past all the checks, the input is an integer in the desired range of values, so, return it
return givenInteger
}
}
}
Save your changes by pressing Command-S.
That's a lot of new code, but your understanding of functions and optionals should help.
Let's take a brief look at how this new code works.
The function accepts three parameters:
prompt
– this is the text that will be shown to the userminimum
– if there is a minimum acceptable value, it should be supplied heremaximum
– if there is a maximum acceptable value, it should be supplied here
The function's logic is very similar to the logic that was explained in the prior tutorial.
The only real difference is that we use optional binding to determine if a minimum (or maximum) value was given.
When that occurs, an additional check is performed to see if the user's input is above the minimum or below the maximum.
Finally, since the function is marked as static that means we can use it without actually creating a variable or constant.
Here's how to invoke, or call, the function in a program:
// Get daytime minutes
var day = Int.collectInput(withPrompt: "Number of daytime minutes? ",
minimum: 0,
maximum: nil)
// Get evening minutes
var evening = Int.collectInput(withPrompt: "Number of evening minutes? ",
minimum: 0,
maximum: nil)
Since the function is static, we can access it directly from the data type, using Int.collectInput...
For that new code to compile without errors, we do need to add the Extensions.swift
file to the list of source code that should be compiled for TheCellSell
.
Select the blue project file, then the TheCellSell
target. Then add Extensions.swift
in the Build Phases section of the target in question, under the Compile Sources subsection.
Let's try running The Cell Sell now. Notice how both prompts for input work, and that the prompts repeat if the user provides incorrect input.
On your own time, I strongly recommend using the Xcode debugger, stepping into the type extension, and getting a feel for how it actually works.
Finally, in any target, or program, you want to use the new extension in, be sure the Extensions.swift
file is compiled along with main.swift
.
Let's do this now for the Trident puzzle.
Select the blue project file, then the Trident target. Then add Extensions.swift
in the Build Phases section of the target in question, under the Compile Sources subsection.
That's it! The extension can now be used in the Trident program as well.
Using extensions in this way reduces repetitive code and decreases the potential for introducing bugs into your programs.