An Overview of Swift Functions and Closures
Previous | Table of Contents | Next |
The Swift Switch Statement | The Basics of Object Oriented Programming in Swift |
<google>BUY_IOS8</google>
Swift functions and closures are a vital part of writing well-structured and efficient code and provide a way to organize programs and avoid code repetition. In this chapter we will look at how functions 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 and, 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.
How to Declare a Swift Function
A Swift function is declared using the following syntax:
func <function name> (<para name>: <para type>, <para name>: <para type>, ... ) -> <return type> { // Function code }
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() { println("Hello") }
The following sample function, on the other hand, takes an integer and a string as parameters and returns a string result:
func buildMessage(name: String, count: Int) -> String { return("\(name), you are customer number \(count)") }
Calling a Swift Function
Once declared, functions are called using the following syntax:
<function name> (<arg1>, <arg2>, ... )
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()
To call a function named buildMessage that takes two parameters and returns a result, on the other hand, we might write the following code:
var message = buildMessage("John", 100)
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.
Declaring 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, the names are local to the function and cannot be referenced when making a function call.
In order to make code easier to read and more self-explanatory, it can be useful to be able to reference the parameter names when a function is called. This can be achieved by declaring the parameter names as being external and involves prefixing the name with a # in the function declaration. Taking the buildMessage function as an example, this would involve the following declaration:
func buildMessage(#name: String, #count: Int) -> String { return("\(name), you are customer number \(count)") }
Once external names have been declared, they must be referenced when calling the function:
var message = buildMessage(name: "John", count: 100)
In the event that the local and external names need to be different, external names can be declared preceding the local name in the function declaration:
func buildMessage(customerName name: String, customerCount count: Int) -> String { return ("\(name), you are customer number \(count)") } var message = buildMessage(customerName: "John", customerCount: 100)
In this case, the local names are used to reference the parameters within the body of the function, while the external names are used to pass the arguments through when calling the function.
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. When using default parameters, it is important that the parameters for which a default is being declared be placed at the end of the parameter list so that the compiler does not become confused about which parameters have been omitted during a function call. 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.
To see default parameters in action the buildMessage 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 buildMessage(count: Int, name: String = "Customer") -> String { return ("\(name), you are customer number \(count)") }
The function can now be called without passing through a customer name value:
var message = buildMessage(100) println(message)
When executed, the above function call will generate output to the console panel which reads:
Customer, you are customer 100
In order to call the function with a customer name, however, it will now be necessary to reference the automatically created parameter name to avoid a syntax error from the compiler:
var message = buildMessage(100, name: "John")
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) { var yards = length * 0.0277778 var centimeters = length * 2.54 var meters = length * 0.0254 return (yards, centimeters, meters) }
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:
-> (yards: Float, centimeters: Float, meters: Float)
Having performed the conversion, the function simply constructs the tuple instance and returns it.
Usage of this function might read as follows:
var lengthTuple = sizeConverter(20) println(lengthTuple.yards) println(lengthTuple.centimeters) println(lengthTuple.meters)
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 { println(string) } } displayStrings("one", "two", "three", "four")
Parameters as Variables
All parameters accepted by a function are declared constants by default preventing changes being made to those parameter values within the function code. If changes to parameters need to be made within the function body, those parameters must be specifically declared as being variable within the function declaration. The following function, for example, is passed length and width parameters in inches and converts those parameters (which have been declared as variables) to centimeters before calculating and returning the area value:
func calcuateArea (var length: Float, var width: Float) -> Float { length = length * 2.54 width = width * 2.54 return length * width } println(calcuateArea(10, 20))
<google>BUY_IOS8</google>
Working with In-Out Parameters
When the value assigned to a variable is passed through as an argument to a function, and that function both declares the parameter as a variable, and specifically changes that value, the question arises as to whether that change is reflected in the original variable. Consider the following code:
var myValue = 10 func doubleValue (var value: Int) -> Int { value += value return(value) } println("Before function call myValue = \(myValue)") println("doubleValue call returns \(doubleValue(myValue))") println("After function call myValue = \(myValue)")
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 which is declared as being a variable. Within the body of the function, the value of the parameter is doubled and returned.
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 = 10
Clearly, the function has made no change to the original myValue variable, even though it was passed through to the function as an argument.
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 prefix the parameter with the inout keyword as follows:
func doubleValue (inout value: Int) -> Int { value += value return(value) }
Finally, when calling the function, the inout parameter must now be prefixed with an & modifier:
println("doubleValue call returned \(doubleValue(&myValue))")
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 = 20
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 = inchesToFeet
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:
var result = toFeet(10)
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.
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) -> Float
A function which 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) -> String
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 = inchesToYards
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:
func outputConversion(converterFunc: (Float) -> Float, value: Float) { var result = converterFunc(value) println("Result of conversion is \(result)") }
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, 10) // Convert to Yards outputConversion(toFeet, 10) // Convert to Inches
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 } }
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 = { println("Hello") } sayHello()
Closure expressions may also be configured to accept parameters and return results. The syntax for this is as follows:
{(<para name>: <para type>, <para name> <para type>, ... ) -> <return type> in // Closure expression code here }
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)
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, simply named closure expressions.
Closure expressions are often 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 from an example used later in the book:
eventStore?.requestAccessToEntityType(EKEntityTypeReminder, completion: {(granted: Bool, error: NSError!) -> Void in if !granted { println(error.localizedDescription) } })
When the tasks performed by the requestAccessToEntityType 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 Bool value and an NSError object as parameters and return no results, hence the following declaration:
{(granted: Bool, error: NSError!) -> Void in
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:
eventStore?.requestAccessToEntityType(EKEntityTypeReminder, completion: {(granted, error) in if !granted { println(error.localizedDescription) } })
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()
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.
To a large extend, 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.
<google>BUY_IOS8</google>
Previous | Table of Contents | Next |
The Swift Switch Statement | The Basics of Object Oriented Programming in Swift |