Russell Gordon

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 user
    • minimum – if there is a minimum acceptable value, it should be supplied here
    • maximum – 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.