Swift Functions, Methods, and Closures

Swift functions, methods, and closures are a vital part of writing well-structured and efficient code and provide a way to organize programs while avoiding code repetition. This chapter will look at how functions, methods, and closures are declared and used within Swift.

What is a Function?

A function is a named block of code that can be called upon to perform a specific task. It can be provided data on which to perform the task and is capable of returning results to the code that called it. For example, if a particular arithmetic calculation needs to be performed in a Swift program, the code to perform the arithmetic can be placed in a function. The function can be programmed to accept the values on which the arithmetic is to be performed (referred to as parameters) and to return the result of the calculation. At any point in the program code where the calculation is required the function is simply called, parameter values passed through as arguments and the result returned.

The terms parameter and argument are often used interchangeably when discussing functions. There is, however, a subtle difference. The values that a function is able to accept when it is called are referred to as parameters. At the point that the function is actually called and passed those values, however, they are referred to as arguments.

What is a Method?

A method is essentially a function that is associated with a particular class, structure, or enumeration. If, for example, you declare a function within a Swift class (a topic covered in detail in the chapter entitled The Basics of Swift Object-Oriented Programming), it is considered to be a method. Although the remainder of this chapter refers to functions, the same rules and behavior apply equally to methods unless otherwise stated.

How to Declare a Swift Function

A Swift function is declared using the following syntax:

 

 

You are reading a sample chapter from iOS 17 App Development Essentials.

Buy the full book now in eBook (PDF and ePub) or Print format.

The full book contains 68 chapters, over 580 pages of in-depth information, and downloadable source code.

Learn more.

Preview  Buy eBook  Buy Print

 

func function name (para name: para type, 
                      para name: para type, ... ) -> return type {
    // Function code
}Code language: plaintext (plaintext)

This combination of function name, parameters and return type are referred to as the function signature.

Explanations of the various fields of the function declaration are as follows:

  • func – The prefix keyword used to notify the Swift compiler that this is a function.
  • <function name> – The name assigned to the function. This is the name by which the function will be referenced when it is called from within the application code.
  • <para name> – The name by which the parameter is to be referenced in the function code.
  • <para type> – The type of the corresponding parameter.
  • <return type> – The data type of the result returned by the function. If the function does not return a result then no return type is specified.
  • Function code – The code of the function that does the work.

As an example, the following function takes no parameters, returns no result and simply displays a message:

func sayHello() {
    print("Hello")
}Code language: Swift (swift)

The following sample function, on the other hand, takes an integer and a string as parameters and returns a string result:

func buildMessageFor(name: String, count: Int) -> String {
       return("\(name), you are customer number \(count)")
}Code language: Swift (swift)

Implicit Returns from Single Expressions

In the previous example, the return statement was used to return the string value from within the buildMessageFor() function. It is worth noting that if a function contains a single expression (as was the case in this example), the return statement may be omitted. The buildMessageFor() method could, therefore, be rewritten as follows:

 

 

You are reading a sample chapter from iOS 17 App Development Essentials.

Buy the full book now in eBook (PDF and ePub) or Print format.

The full book contains 68 chapters, over 580 pages of in-depth information, and downloadable source code.

Learn more.

Preview  Buy eBook  Buy Print

 

func buildMessageFor(name: String, count: Int) -> String {
       "\(name), you are customer number \(count)"
}Code language: Swift (swift)

The return statement can only be omitted if the function contains a single expression. The following code, for example, will fail to compile since the function contains two expressions requiring the use of the return statement:

func buildMessageFor(name: String, count: Int) -> String {
       let uppername = name.uppercased()
       "\(uppername), you are customer number \(count)" // Invalid expression
}Code language: Swift (swift)

Calling a Swift Function

Once declared, functions are called using the following syntax:

function-name (arg1, arg2, ... )Code language: plaintext (plaintext)

Each argument passed through to a function must match the parameters the function is configured to accept. For example, to call a function named sayHello that takes no parameters and returns no value, we would write the following code:

sayHello()Code language: Swift (swift)

Handling Return Values

To call a function named buildMessageFor that takes two parameters and returns a result, on the other hand, we might write the following code:

let message = buildMessageFor(name: "John", count: 100)Code language: Swift (swift)

In the above example, we have created a new variable called message and then used the assignment operator (=) to store the result returned by the function.

 

 

You are reading a sample chapter from iOS 17 App Development Essentials.

Buy the full book now in eBook (PDF and ePub) or Print format.

The full book contains 68 chapters, over 580 pages of in-depth information, and downloadable source code.

Learn more.

Preview  Buy eBook  Buy Print

 

When developing in Swift, situations may arise where the result returned by a method or function call is not used. When this happens, the return value may be discarded by assigning it to ‘_’. For example:

_ = buildMessageFor(name: "John", count: 100)Code language: Swift (swift)

Local and External Parameter Names

When the preceding example functions were declared, they were configured with parameters that were assigned names which, in turn, could be referenced within the body of the function code. When declared in this way, these names are referred to as local parameter names.

In addition to local names, function parameters may also have external parameter names. These are the names by which the parameter is referenced when the function is called. By default, function parameters are assigned the same local and external parameter names. Consider, for example, the previous call to the buildMessageFor method: let message = buildMessageFor(name: “John”, count: 100)

As declared, the function uses “name” and “count” as both the local and external parameter names.

The default external parameter names assigned to parameters may be removed by preceding the local parameter names with an underscore (_) character as follows:

 

 

You are reading a sample chapter from iOS 17 App Development Essentials.

Buy the full book now in eBook (PDF and ePub) or Print format.

The full book contains 68 chapters, over 580 pages of in-depth information, and downloadable source code.

Learn more.

Preview  Buy eBook  Buy Print

 

func buildMessageFor(_ name: String, _ count: Int) -> String {
       return("\(name), you are customer number \(count)")
}Code language: Swift (swift)

With this change implemented, the function may now be called as follows:

let message = buildMessageFor("John", 100)Code language: Swift (swift)

Alternatively, external parameter names can be added simply by declaring the external parameter name before the local parameter name within the function declaration. In the following code, for example, the external names of the first and second parameters have been set to “username” and “usercount” respectively:

func buildMessageFor(username name: String, usercount count: Int) 
                                                         -> String {
       return("\(name), you are customer number \(count)")
}Code language: Swift (swift)

When declared in this way, the external parameter name must be referenced when calling the function:

let message = buildMessageFor(username: "John", usercount: 100)Code language: Swift (swift)

Regardless of the fact that the external names are used to pass the arguments through when calling the function, the local names are still used to reference the parameters within the body of the function. It is important to also note that when calling a function using external parameter names for the arguments, those arguments must still be placed in the same order as that used when the function was declared.

Declaring Default Function Parameters

Swift provides the ability to designate a default parameter value to be used in the event that the value is not provided as an argument when the function is called. This simply involves assigning the default value to the parameter when the function is declared. Swift also provides a default external name based on the local parameter name for defaulted parameters (unless one is already provided) which must then be used when calling the function.

 

 

You are reading a sample chapter from iOS 17 App Development Essentials.

Buy the full book now in eBook (PDF and ePub) or Print format.

The full book contains 68 chapters, over 580 pages of in-depth information, and downloadable source code.

Learn more.

Preview  Buy eBook  Buy Print

 

To see default parameters in action the buildMessageFor function will be modified so that the string “Customer” is used as a default in the event that a customer name is not passed through as an argument:

func buildMessageFor(_ name: String = "Customer", count: Int ) -> String
{
    return ("\(name), you are customer number \(count)")
}Code language: Swift (swift)

The function can now be called without passing through a name argument:

let message = buildMessageFor(count: 100)
print(message)Code language: Swift (swift)

When executed, the above function call will generate output to the console panel which reads:

Customer, you are customer number 100Code language: plaintext (plaintext)

Returning Multiple Results from a Function

A function can return multiple result values by wrapping those results in a tuple. The following function takes as a parameter a measurement value in inches. The function converts this value into yards, centimeters and meters, returning all three results within a single tuple instance:

func sizeConverter(_ length: Float) -> (yards: Float, centimeters: Float,
                                        meters: Float) {
 
    let yards = length * 0.0277778
    let centimeters = length * 2.54
    let meters = length * 0.0254
 
    return (yards, centimeters, meters)
}Code language: Swift (swift)

The return type for the function indicates that the function returns a tuple containing three values named yards, centimeters and meters respectively, all of which are of type Float:

 

 

You are reading a sample chapter from iOS 17 App Development Essentials.

Buy the full book now in eBook (PDF and ePub) or Print format.

The full book contains 68 chapters, over 580 pages of in-depth information, and downloadable source code.

Learn more.

Preview  Buy eBook  Buy Print

 

-> (yards: Float, centimeters: Float, meters: Float)Code language: Swift (swift)

Having performed the conversion, the function simply constructs the tuple instance and returns it. Usage of this function might read as follows:

let lengthTuple = sizeConverter(20)
 
print(lengthTuple.yards)
print(lengthTuple.centimeters)
print(lengthTuple.meters)Code language: Swift (swift)

Variable Numbers of Function Parameters

It is not always possible to know in advance the number of parameters a function will need to accept when it is called within application code. Swift handles this possibility through the use of variadic parameters. Variadic parameters are declared using three periods (…) to indicate that the function accepts zero or more parameters of a specified data type. Within the body of the function, the parameters are made available in the form of an array object. The following function, for example, takes as parameters a variable number of String values and then outputs them to the console panel:

func displayStrings(_ strings: String...)
{
    for string in strings {
        print(string)
    }
}
 
displayStrings("one", "two", "three", "four")Code language: Swift (swift)

Parameters as Variables

All parameters accepted by a function are treated as constants by default. This prevents changes being made to those parameter values within the function code. If changes to parameters need to be made within the function body, therefore, shadow copies of those parameters must be created. The following function, for example, is passed length and width parameters in inches, creates shadow variables of the two values and converts those parameters to centimeters before calculating and returning the area value:

func calcuateArea(length: Float, width: Float) -> Float {
 
    var length = length
    var width = width
 
    length = length * 2.54
    width = width * 2.54
    return length * width
}
 
print(calcuateArea(length: 10, width: 20))Code language: Swift (swift)

Working with In-Out Parameters

When a variable is passed through as a parameter to a function, we now know that the parameter is treated as a constant within the body of that function. We also know that if we want to make changes to a parameter value we have to create a shadow copy as outlined in the above section. Since this is a copy, any changes made to the variable are not, by default, reflected in the original variable. Consider, for example, the following code:

var myValue = 10
 
func doubleValue (_ value: Int) -> Int {
    var value = value
    value += value
    return(value)
}
 
print("Before function call myValue = \(myValue)")
 
print("doubleValue call returns \(doubleValue(myValue))")
 
print("After function call myValue = \(myValue)")Code language: Swift (swift)

The code begins by declaring a variable named myValue initialized with a value of 10. A new function is then declared which accepts a single integer parameter. Within the body of the function, a shadow copy of the value is created, doubled and returned.

 

 

You are reading a sample chapter from iOS 17 App Development Essentials.

Buy the full book now in eBook (PDF and ePub) or Print format.

The full book contains 68 chapters, over 580 pages of in-depth information, and downloadable source code.

Learn more.

Preview  Buy eBook  Buy Print

 

The remaining lines of code display the value of the myValue variable before and after the function call is made.

When executed, the following output will appear in the console:

Before function call myValue = 10
doubleValue call returns 20
After function call myValue = 1Code language: plaintext (plaintext)

Clearly, the function has made no change to the original myValue variable. This is to be expected since the mathematical operation was performed on a copy of the variable, not the myValue variable itself.

In order to make any changes made to a parameter persist after the function has returned, the parameter must be declared as an in-out parameter within the function declaration. To see this in action, modify the doubleValue function to include the inout keyword, and remove the creation of the shadow copy as follows:

func doubleValue (_ value: inout Int) -> Int {
    value += value
    return(value)
}Code language: Swift (swift)

Finally, when calling the function, the inout parameter must now be prefixed with an & modifier:

 

 

You are reading a sample chapter from iOS 17 App Development Essentials.

Buy the full book now in eBook (PDF and ePub) or Print format.

The full book contains 68 chapters, over 580 pages of in-depth information, and downloadable source code.

Learn more.

Preview  Buy eBook  Buy Print

 

print("doubleValue call returned \(doubleValue(&myValue))")Code language: Swift (swift)

Having made these changes, a test run of the code should now generate output clearly indicating that the function modified the value assigned to the original myValue variable:

Before function call myValue = 10
doubleValue call returns 20
After function call myValue = 2Code language: plaintext (plaintext)

Functions as Parameters

An interesting feature of functions within Swift is that they can be treated as data types. It is perfectly valid, for example, to assign a function to a constant or variable as illustrated in the declaration below:

func inchesToFeet (_ inches: Float) -> Float {
    return inches * 0.0833333
}
 
let toFeet = inchesToFeetCode language: Swift (swift)

The above code declares a new function named inchesToFeet and subsequently assigns that function to a constant named toFeet. Having made this assignment, a call to the function may be made using the constant name instead of the original function name:

let result = toFeet(10)Code language: Swift (swift)

On the surface this does not seem to be a particularly compelling feature. Since we could already call the function without assigning it to a constant or variable data type it does not seem that much has been gained.

The possibilities that this feature offers become more apparent when we consider that a function assigned to a constant or variable now has the capabilities of many other data types. In particular, a function can now be passed through as an argument to another function, or even returned as a result from a function.

 

 

You are reading a sample chapter from iOS 17 App Development Essentials.

Buy the full book now in eBook (PDF and ePub) or Print format.

The full book contains 68 chapters, over 580 pages of in-depth information, and downloadable source code.

Learn more.

Preview  Buy eBook  Buy Print

 

Before we look at what is, essentially, the ability to plug one function into another, it is first necessary to explore the concept of function data types. The data type of a function is dictated by a combination of the parameters it accepts and the type of result it returns. In the above example, since the function accepts a floating-point parameter and returns a floating-point result, the function’s data type conforms to the following:

(Float) -> FloatCode language: Swift (swift)

A function that accepts an Int and a Double as parameters and returns a String result, on the other hand, would have the following data type:

(Int, Double) -> StringCode language: Swift (swift)

In order to accept a function as a parameter, the receiving function simply declares the data type of the function it is able to accept.

For the purposes of an example, we will begin by declaring two unit conversion functions and assigning them to constants:

func inchesToFeet (_ inches: Float) -> Float {
 
    return inches * 0.0833333
}
 
func inchesToYards (_ inches: Float) -> Float {
 
    return inches * 0.0277778
}
 
let toFeet = inchesToFeet
let toYards = inchesToYardsCode language: Swift (swift)

The example now needs an additional function, the purpose of which is to perform a unit conversion and print the result in the console panel. This function needs to be as general purpose as possible, capable of performing a variety of different measurement unit conversions. In order to demonstrate functions as parameters, this new function will take as a parameter a function type that matches both the inchesToFeet and inchesToYards function data type together with a value to be converted. Since the data type of these functions is equivalent to (Float) -> Float, our general-purpose function can be written as follows:

 

 

You are reading a sample chapter from iOS 17 App Development Essentials.

Buy the full book now in eBook (PDF and ePub) or Print format.

The full book contains 68 chapters, over 580 pages of in-depth information, and downloadable source code.

Learn more.

Preview  Buy eBook  Buy Print

 

func outputConversion(_ converterFunc: (Float) -> Float, value: Float) {
 
    let result = converterFunc(value)
 
    print("Result of conversion is \(result)")
}Code language: Swift (swift)

When the outputConversion function is called, it will need to be passed a function matching the declared data type. That function will be called to perform the conversion and the result displayed in the console panel. This means that the same function can be called to convert inches to both feet and yards, simply by “plugging in” the appropriate converter function as a parameter. For example:

outputConversion(toYards, value: 10) // Convert to Yards
outputConversion(toFeet, value: 10) // Convert to FeetCode language: Swift (swift)

Functions can also be returned as a data type simply by declaring the type of the function as the return type. The following function is configured to return either our toFeet or toYards function type (in other words a function which accepts and returns a Float value) based on the value of a Boolean parameter:

func decideFunction(_ feet: Bool) -> (Float) -> Float
{
    if feet {
        return toFeet
    } else {
        return toYards
    }
}Code language: Swift (swift)

Closure Expressions

Having covered the basics of functions in Swift it is now time to look at the concept of closures and closure expressions. Although these terms are often used interchangeably there are some key differences.

Closure expressions are self-contained blocks of code. The following code, for example, declares a closure expression and assigns it to a constant named sayHello and then calls the function via the constant reference:

let sayHello = { print("Hello") }
sayHello()Code language: Swift (swift)

Closure expressions may also be configured to accept parameters and return results. The syntax for this is as follows:

 

 

You are reading a sample chapter from iOS 17 App Development Essentials.

Buy the full book now in eBook (PDF and ePub) or Print format.

The full book contains 68 chapters, over 580 pages of in-depth information, and downloadable source code.

Learn more.

Preview  Buy eBook  Buy Print

 

{(para name: para type, para name para type, ... ) -> 
                                                return type in
         // Closure expression code here
}Code language: plaintext (plaintext)

The following closure expression, for example, accepts two integer parameters and returns an integer result:

let multiply = {(_ val1: Int, _ val2: Int) -> Int in 
    return val1 * val2 
} 
let result = multiply(10, 20)Code language: Swift (swift)

Note that the syntax is similar to that used for declaring Swift functions with the exception that the closure expression does not have a name, the parameters and return type are included in the braces and the in keyword is used to indicate the start of the closure expression code. Functions are, in fact, just named closure expressions.

Before the introduction of structured concurrency in Swift 5.5 (a topic covered in detail in the chapter entitled “An Overview of Swift Structured Concurrency”), closure expressions were often (and still are) used when declaring completion handlers for asynchronous method calls. In other words, when developing iOS applications, it will often be necessary to make calls to the operating system where the requested task is performed in the background allowing the application to continue with other tasks. Typically, in such a scenario, the system will notify the application of the completion of the task and return any results by calling the completion handler that was declared when the method was called. Frequently the code for the completion handler will be implemented in the form of a closure expression. Consider the following code example:

eventstore.requestAccess(to: .reminder, completion: {(granted: Bool, 
                    error: Error?) -> Void in
    if !granted {
            print(error!.localizedDescription)
    }
})Code language: Swift (swift)

When the tasks performed by the requestAccess(to:) method call are complete it will execute the closure expression declared as the completion: parameter. The completion handler is required by the method to accept a Boolean value and an Error object as parameters and return no results, hence the following declaration:

{(granted: Bool, error: Error?) -> Void inCode language: Swift (swift)

In actual fact, the Swift compiler already knows about the parameter and return value requirements for the completion handler for this method call and is able to infer this information without it being declared in the closure expression. This allows a simpler version of the closure expression declaration to be written:

 

 

You are reading a sample chapter from iOS 17 App Development Essentials.

Buy the full book now in eBook (PDF and ePub) or Print format.

The full book contains 68 chapters, over 580 pages of in-depth information, and downloadable source code.

Learn more.

Preview  Buy eBook  Buy Print

 

eventstore.requestAccess(to: .reminder, completion: {(granted, error) in
    if !granted {
            print(error!.localizedDescription)
    }
})Code language: Swift (swift)

Shorthand Argument Names

A useful technique for simplifying closures involves using shorthand argument names. This allows the parameter names and “in” keyword to be omitted from the declaration and the arguments to be referenced as $0, $1, $2 etc. Consider, for example, a closure expression designed to concatenate two strings:

let join = { (string1: String, string2: String) -> String in 
    string1 + string2
}Code language: Swift (swift)

Using shorthand argument names, this declaration can be simplified as follows:

let join: (String, String) -> String = {
    $0 + $1 
}Code language: Swift (swift)

Note that the type declaration ((String, String) -> String) has been moved to the left of the assignment operator since the closure expression no longer defines the argument or return types.

Closures in Swift

A closure in computer science terminology generally refers to the combination of a self-contained block of code (for example a function or closure expression) and one or more variables that exist in the context surrounding that code block. Consider, for example the following Swift function:

func functionA() -> () -> Int {
 
    var counter = 0
 
    func functionB() -> Int {
        return counter + 10
    }
    return functionB
}
 
let myClosure = functionA()
let result = myClosure()Code language: Swift (swift)

In the above code, functionA returns a function named functionB. In actual fact functionA is returning a closure since functionB relies on the counter variable which is declared outside the functionB’s local scope. In other words, functionB is said to have captured or closed over (hence the term closure) the counter variable and, as such, is considered a closure in the traditional computer science definition of the word.

 

 

You are reading a sample chapter from iOS 17 App Development Essentials.

Buy the full book now in eBook (PDF and ePub) or Print format.

The full book contains 68 chapters, over 580 pages of in-depth information, and downloadable source code.

Learn more.

Preview  Buy eBook  Buy Print

 

To a large extent, and particularly as it relates to Swift, the terms closure and closure expression have started to be used interchangeably. The key point to remember, however, is that both are supported in Swift.

Summary

Functions, closures, and closure expressions are self-contained blocks of code that can be called upon to perform a specific task and provide a mechanism for structuring code and promoting reuse. This chapter has introduced the concepts of functions and closures in terms of declaration and implementation.


Categories