In this post, we’re going to learn about how to define and call various types of Swift functions. A function is a self-contained block of code whose purpose is to perform a specific task. We use functions so that we can break up the main source code into manageable chunks. Also, if we need to repeat code, we can simply call the function instead of copying-and-pasting the code.
Functions have 3 main parts: the function name, any input parameters, and any return type. All functions need names, but not all need any input parameters or need to return a value. In fact, we’ve actually been using functions this entire time! The print(…) function that allows us to print to the preview pane is a function that takes parameters and doesn’t return anything. We’re going to see how we can declare and use functions with parameters and a return type; we’ll also learn some Swift shorthand and extra functionality that Swift provides over other programming languages.
BUILD GAMES
FINAL DAYS: Unlock 250+ coding courses, guided learning paths, help from expert mentors, and more.
Functions without Return Types or Parameters
Also called procedures, we can declare a function with no input parameters or return like the following.
func printHello() { print("Hello!") } printHello()
First comes the keyword func and then the name of the function. Afterwards, any input parameters will come after in the parentheses, but we don’t have any. Then comes the function body in a set of curly braces. In the last line, we call the function, and it will execute the code. Now that we have this function, we can call it as many times as needed and the same block of code will execute.
Functions with Parameters
Inside of the parentheses, we can declare any number of parameters that we can pass to the function. Take the following function, for example.
func sayHi(_ firstName: String, lastName: String) { print("Hi " + firstName + " " + lastName + "!") } sayHi("Bob", lastName:"Smith")
In the parentheses, we have a comma-separated list of input parameters to the function. Each one needs a parameter name so we can refer to them inside of the function, and each one needs a type annotation as well. After declaring these parameters, we can use them in the function. We can call the function and supply in the input parameters. Note that the first parameter can just be passed through a literal or variable, but every subsequent parameter needs to be labeled using the parameter name in the function declaration. This is where Xcode will help by automatically filling in those parameter names. We’ll discuss these parameter names a bit later in this section.
Functions with Return Types
Functions can return values as well as take in input. Consider the following squaring function.
func squareNum(_ x: Double) -> Double { return x * x; } print(squareNum(10))
We can specify a return type by putting the return arrow (->) after the parentheses and specifying a return type. This means that somewhere in the function, usually at the end, we need to say return and then some value that corresponds to the type we’re returning. When we call this function, we can set the return value to a variable or we can ignore it altogether.
Swift also supports returning multiple values in the form of tuples. We can change the type after the return arrow to be a tuple. The tuple follows all of the characteristics of Swift tuple, including naming the tuple values. Take the following square root function.
func squareRoot(_ x: Double) -> (positiveSqrt: Double, negativeSqrt: Double) { let posSqrt = sqrt(fabs(x)) let negSqrt = -sqrt(fabs(x)) return (posSqrt, negSqrt) } print(squareRoot(25))
In this function, we’re returning a tuple that has two fields, both being Doubles and named. We don’t have to name the tuple members, but it’s good practice to do so we don’t have to refer to them through indices. In addition to these tuple types, we can even have optional tuples whose value with can check using value binding. Here’s an example of how we can return optional tuples.
func squareRoot(_ x: Double) -> (positiveSqrt: Double, negativeSqrt: Double)? { if x < 0 { return nil } let posSqrt = sqrt(fabs(x)) let negSqrt = -sqrt(fabs(x)) return (posSqrt, negSqrt) } if let val = squareRoot(25) { print(squareRoot(25)!) }
Notice how there is a question mark at the end of the return tuple when declaring the function. Since it returns an optional, we now have to use value binding when dealing with the return type since it’s not guaranteed to be non-nil.
Function Parameters
We saw in a previous example how to use functions with multiple parameters. In this subsection, we’re going to expand on the different things we can do with function parameters.
External Parameter Names
In the example on multiple parameters, note how we had to label each input with the parameter name starting with the second parameter. We can change the parameter names so that they make more sense to someone calling the method while retaining our name context for the parameter.
func sayHi(person personToSayHiTo: String, times numTimes: Int) { for var i = 0; i < numTimes; i++ { print("Hi " + personToSayHiTo + "!") } } sayHi(person: "Bob", times: 3)
As a side note, giving the first parameter a name will force the caller of our function to use that external parameter name instead of just passing in the input. We’ll talk more about removing this label in the next subsection. Anyway, as we can see above, the first name is the external name of the parameter while the second name is the internal name, used inside the function. When we call the function, we can use the external names instead of the internal names. This allows us to separate the two different contexts: implementer of the function and caller of the function.
Omitting External Parameter Names
One issue with the last code snippet was that we had to label the input with the external parameter name. Even in the first code snippet with multiple parameters, we have to label the inputs starting with the second parameter. Using parameter names is a good practice, but it in some contexts, it might be unnecessary because the function and its parameters are intuitive enough for callers to understand. We can allow our user to skip the labeling if we substitute an underscore (_) for the external parameter name. For example, take the following function. We need to always provide an external label or an underscore.
func doublePower(_ base: Double, _ exponent: Double) -> Double { return pow(base, exponent * exponent) } print(doublePower(2, 2))
Note that we don’t have to do this for the first parameter since it doesn’t require a label by default. Now that we don’t need to label the second parameter, we can just pass in the value directly.
Default Parameter Values
For any parameter, we can give it a default value by assigning the parameter a value in the parentheses right after the parameter type. If a function’s parameter has a default value, we can omit that parameter when calling the function.
func doublePower(_ base: Double, _ exponent: Double = 2) -> Double { return pow(base, exponent * exponent) } print(doublePower(2))
As a point of good practice, it’s a good idea to put all of the parameters with default values at the end of the parameter list so that callers of our function start off with the same format for the parameters without default values.
It is very important to note that ANY CHANGES MADE TO A VARIABLE PARAMETER IN THE FUNCTION DO NOT PERSIST! This means that if we were to modify the variable parameter, it wouldn’t alter the original variable that the caller passed into the function. However, there is a different way we can solve this problem, as illustrated in the next section.
Parameter References
Swift does provide us a way to allow changes to parameters to persist even after the function is called. A classic example is a function to swap the value of two variable. Beginning programmers will try to write a swap function that swaps values but forget that copies of the variables are made when a function is called. Those changes don’t persist! However, we can pass in the parameter references so that that changes will persist after the function call. To do this, we must add the inout modifier to the function parameter declaration.
func swap(_ x: inout Int, _ y: inout Int) { let temp = x; x = y y = temp } var a = 3 var b = 4 swap(&a, &b)
In the above code, by saying that the parameters are inout , we’re allowed to modify the variables directly, not just copies! When we’re passing values into a function that has inout parameters, we need to use ampersands (&) so that the caller of the function, us in this case, knows that the function will modify the variables directly. Now we can see that the variable’s values have been successfully swapped!
Functions as Types
Each function that we define has a “type” or signature that is the combination of the input parameters and return type. Swift allows us to use this property of functions to define variables that represent functions. Let’s see an example of this to make the concept clearer.
func addInts(_ x: Int, _ y: Int) -> Int { return x + y } func subtractInts(_ x: Int, _ y: Int) -> Int { return x - y } var mathOp: (Int, Int) -> Int = addInts mathOp(3, 4) mathOp = subtractInts mathOp(3, 4)
In the above code snippet, we declare two functions with the same signature. Next we have a variable that we set to a function! The type of the variable is “a function that takes two integers and returns an integer”. After we assign this variable, it essentially acts as an alias to the function it’s assigned to! We can change this variable to be another function, subtractInts, and when we “call” the variable, it will route to subtractInts instead. Swift treats functions as first-class variables so we can use them as parameters and return types as we’re going to see.
We can use a function as a parameter by using the signature as the type annotation as seen in the function below. We’re saying that the parameter is a function that is guaranteed to take two integers and return an integer.
func executeMathFunc(_ mathOp: (Int, Int) -> Int, _ x: Int, _ y: Int) -> Int { return mathOp(x, y) } print (executeMathFunc(addInts, 3, 4))
Now we can treat it as a function and pass it the input values. This allows us to pass in any function that takes two integers and returns an integer. We can also use functions as return types of other functions.
func chooseAddSub(_ add: Bool) -> (Int, Int) -> Int { return add ? addInts : subtractInts } var getFunc = chooseAddSub(true) print(getFunc(1, 2))
The above code has a function that returns a function! Don’t get confused by the arrows, though. The first arrow is the return type of the function and the second tells us that the returned function is guaranteed to return an integer.
To recap, in this section, we learned all about functions. We saw how to give functions multiple parameters as well as a return type. We also saw how to name external parameters and the various modifications we can add to parameters to allows us to modify those parameters. Finally, we learned how to use functions as variables, both as parameters and return types of other functions.