Swift Learning (8) - Functions (Improved Code Version)
A function is an independent code segment that completes a specific task. You can identify a function’s purpose by its name, which can be used to “call” the function whenever you need it to perform its task.
Swift’s unified function syntax is very flexible and can represent any function, from the simplest C-style functions without parameter names to complex Objective-C-style functions with local and external parameter names. Parameters can provide default values to simplify function calls. Parameters can also be used as both input and output parameters, meaning that once the function finishes executing, the values of the input parameters may be modified.
In Swift, every function has a type composed of the types of its parameter values and return value. You can treat function types like any other ordinary variable type, making it easier to pass functions as parameters to other functions or return functions from other functions. Function definitions can also be written inside other function definitions, enabling encapsulation of functionality within nested function scopes.
Function Definition and Calling
When defining a function, you can define one or more named and typed values as the function’s input, called parameters, and you can also define a value of a certain type as the output when the function finishes executing, called the return type.
Every function has a function name that describes the task it performs. To use a function, call it by its name and pass it matching input values (called arguments). The arguments must be in the same order as the parameters in the function’s parameter list.
In the example below, the function is named greet(person:)
. It takes a person’s name as input and returns a greeting for that person. To accomplish this, it defines an input parameter: a String
value called person
, and a return value of type String
containing the greeting for that person.
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}
All of this information together is called the function’s definition, and it starts with the func
keyword. To specify the return type, use a return arrow ->
(a hyphen followed by a right angle bracket) followed by the name of the return type.
This definition describes what the function does, what it expects as parameters, and what type of result it returns when finished. Such a definition allows the function to be called elsewhere in a clear way:
print(greet(person: "Jensen")) // Prints "Hello, Jensen!"
print(greet(person: "Morris")) // Prints "Hello, Morris!"
---
output: Hello, Jensen!
Hello, Morris!
When calling the greet(person:)
function, you pass it a String
argument in parentheses, such as greet(person: "Anna")
. As shown above, since this function returns a String
, greet
can be included in a call to print(_:separator:terminator:)
to output the function’s return value.
Note
The first parameter of the
print(_:separator:terminator:)
function does not have a label, and the other parameters are optional because they have default values. For more details about these function syntax variations, see the section below on function parameter labels, parameter names, and default parameter values.
In the body of greet(person:)
, a new constant named greeting
is defined, and the greeting message for personName
is assigned to it. Then, the return
keyword is used to return the greeting. Once return greeting
is called, the function ends its execution and returns the current value of greeting
.
To simplify this definition, you can write the creation and return of the greeting message in one line:
func greetAgain(person: String) -> String {
return "Hello again, " + person + "!"
}
print(greetAgain(person: "Jensen")) // Prints "Hello again, Jensen!"
---
output: Hello again, Jensen!
Function Parameters and Return Values
Function parameters and return values are very flexible in Swift. You can define any type of function, from simple functions with a single unnamed parameter to complex functions with expressive parameter names and various parameter options.
Functions with No Parameters
A function can have no parameters. The following function is a parameterless function that returns a fixed String
message when called:
func sayHelloWorld() -> String {
return "Hello, World!"
}
print(sayHelloWorld()) // Prints "Hello, World!"
---
output: Hello, World!
Even though this function has no parameters, you still need a pair of parentheses after the function name in its definition and when calling it.
Functions with Multiple Parameters
A function can have multiple input parameters, which are included in the function’s parentheses and separated by commas.
The following function takes a person’s name and a Boolean indicating whether they have already been greeted, and returns an appropriate greeting:
func greet(person: String, alreadyGreeted: Bool) -> String {
if alreadyGreeted {
return greetAgain(person: person)
} else {
return greet(person: person)
}
}
print(greet(person: "Jensen", alreadyGreeted: true)) // Prints "Hello again, Jensen!"
---
output: Hello again, Jensen!
You can call the greet(person:alreadyGreeted:)
function by passing a String
value and a Bool
value labeled alreadyGreeted
, separated by a comma inside the parentheses. Note that this function is different from the greet(person:)
function above. Although they share the same name, greet(person:alreadyGreeted:)
requires two parameters, while greet(person:)
requires only one.
Functions with No Return Value
A function can have no return value. Here is another version of the greet(person:)
function, called greets
, which prints a String
value directly instead of returning it:
func greets(person: String) {
print("Hello, \(person)!")
}
greets(person: "Jensen") // Prints "Hello, Jensen!"
---
output: Hello, Jensen!
Since this function does not need to return a value, there is no return arrow ->
or return type in its definition.
Note
Strictly speaking, even if no return value is explicitly defined, the
greet(person:)
function still returns a value. Functions without an explicit return type return a special value of typeVoid
, which is an empty tuple written as ().
You can ignore the return value when calling a function:
func printAndCount(string: String) -> Int {
print(string)
return string.count
}
func printWithoutCounting(string: String) {
let _ = printAndCount(string: string)
}
printAndCount(string: "Hello, World") // Prints "Hello, World" and returns 12
printWithoutCounting(string: "Hello, World") // Prints "Hello, World" but returns nothing
---
output: Hello, World
Hello, World
The first function, printAndCount(string:)
, prints a string and returns the character count as an Int
. The second function, printWithoutCounting(string:)
, calls the first function but ignores its return value. When the second function is called, the message is still printed by the first function, but the return value is not used.
Note
Return values can be ignored, but a function defined with a return value must return a value. If you do not return a value at the end of the function, it will result in a compile-time error.
Functions with Multiple Return Values
You can use tuple types to return multiple values as a composite value from a function.
The following example defines a function called minMax(array:)
that finds the minimum and maximum values in an array of Int
values.
func minMax(array: [Int]) -> (min: Int, max: Int) {
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
The minMax(array:)
function returns a tuple containing two Int
values, labeled min
and max
, so you can access them by name.
In the body of minMax(array:)
, two working variables, currentMin
and currentMax
, are initialized to the first value in the array. The function then iterates over the remaining values in the array, checking if each value is smaller than currentMin
or larger than currentMax
. Finally, the minimum and maximum values are returned as a tuple.
Since the tuple members are named, you can access the minimum and maximum values using dot syntax:
let bounds = minMax(array: [8, -6, 2, 109, 3, -71])
print("Min is \(bounds.min) and max is \(bounds.max).") // Prints "Min is -71 and max is 109."
---
output: Min is -71 and max is 109.
Note that you do not need to name the tuple members when returning the tuple from the function, as their names are already specified in the function’s return type.
Optional Tuple Return Types
If a function’s tuple return type might not have a value at all, you can use an optional tuple return type to reflect that the entire tuple can be nil
. You define an optional tuple by placing a question mark after the closing parenthesis, such as (Int, Int)?
or (String, Int, Bool)?
Note
An optional tuple type like
(Int, Int)?
is different from a tuple containing optional types like(Int?, Int?)
. An optional tuple means the entire tuple is optional, not just the individual elements.
The previous minMax(array:)
function returns a tuple containing two Int
values, but it does not perform any safety checks on the input array. If the array
parameter is empty, accessing array[0]
will cause a runtime error.
To safely handle the “empty array” case, rewrite the minMax(array:)
function to use an optional tuple return type and return nil
when the array is empty:
func MinMax(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty {
return nil
}
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
You can use optional binding to check whether the minMax(array:)
function returns a tuple value or nil
:
if let bounds = MinMax(array: [8, -6, 2, 109, 3, -71]) {
print("Min is \(bounds.min) and max is \(bounds.max).")
} // Prints "Min is -71 and max is 109."
if let bounds = MinMax(array: []) {
print("Min is \(bounds.min) and max is \(bounds.max).")
} else {
print("Array is null.")
}
---
output: Min is -71 and max is 109.
Array is null.
Implicitly Returned Functions
If the entire body of a function is a single expression, the function can implicitly return that expression. For example, the following functions have the same effect:
func greeting(for person: String) -> String {
"Hello, " + person + "!"
}
print(greeting(for: "Jensen")) // Prints "Hello, Jensen!"
func anotherGreeting(for person: String) -> String {
return "Hello, " + person + "!"
}
print(anotherGreeting(for: "Jensen")) // Prints "Hello, Jensen!"
---
output: Hello, Jensen!
Hello, Jensen!
The complete definition of the greeting(for:)
function is the return of the greeting content, which means it can use the more concise implicit return form. The anotherGreeting(for:)
function returns the same content but is longer because of the return
keyword. Any function that can be written as a single return
statement can omit return
.
As you will see in the “Shorthand Getter Declaration” section, a property’s getter can also use the implicit return form.
Function Parameter Labels and Parameter Names
Every function parameter has a parameter label (Argument Label) and a parameter name (Parameter Name). The parameter label is used when calling the function; you need to write the parameter label before the corresponding argument. The parameter name is used in the function’s implementation. By default, function parameters use their parameter names as their parameter labels.
func someFunction(firstParameterName: Int, secondParameterName: Int) {
// Inside the function body, firstParameterName and secondParameterName refer to the first and second parameter values
}
someFunction(firstParameterName: 1, secondParameterName: 2)
All parameters must have a unique name. Although it is possible for multiple parameters to have the same parameter label, a unique parameter label makes the code more readable.
Specifying Parameter Labels
You can specify a parameter label before the parameter name, separated by a space:
func someFunction(argumentLabel parameterName: Int) {
// Inside the function body, parameterName refers to the parameter value
}
The following version of the greet(person:)
function takes a person’s name and their hometown, and returns a greeting:
func greet(person: String, from hometown: String) -> String {
"Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Jensen", from: "Hefei")) // Prints "Hello Jensen! Glad you could visit from Hefei."
---
output: Hello Jensen! Glad you could visit from Hefei.
Using parameter labels makes a function call more expressive and natural, while still maintaining readability and clear intent inside the function.
Omitting Parameter Labels
If you do not want to add a label for a parameter, you can use an underscore _
to replace an explicit parameter label.
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// Inside the function body, firstParameterName and secondParameterName refer to the first and second parameter values
}
someFunction(1, secondParameterName: 2)
If a parameter has a label, you must use the parameter label when calling the function.
Default Parameter Values
You can define a default value for any parameter by assigning a value to it in the function body. When a default value is defined, you can omit that parameter when calling the function.
func someFunction(parameterWithoutDefault: Int, parameterWithDeafult: Int = 12) {
// If the second parameter is not passed when calling, parameterWithDefault will be assigned the default value 12 in the function body.
}
someFunction(parameterWithoutDefault: 1) // parameterWithDefault is 12
someFunction(parameterWithoutDefault: 2, parameterWithDeafult: 6) // parameterWithDefault is 6
Place parameters without default values at the beginning of the parameter list. Generally, parameters without default values are more important, and placing them first ensures that the order of non-default parameters is consistent when calling the function, making calls to the same function in different situations clearer.
Variadic Parameters
A variadic parameter can accept zero or more values. When calling a function, you can use a variadic parameter to specify that the parameter can accept an indeterminate number of input values. You define a variadic parameter by adding ...
after the parameter’s type name.
The values passed to a variadic parameter become an array of that type inside the function body. For example, a variadic parameter called numbers
of type Double...
becomes a constant array of type [Double]
named numbers
inside the function body.
The following function calculates the arithmetic mean of a group of numbers of any length:
func arithmeticMean(_ numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
print(arithmeticMean(1, 2, 3, 4, 5)) // Prints 3.0, the mean of these five numbers
print(arithmeticMean(3, 8.25, 18.75)) // Prints 10.0, the mean of these three numbers
---
output: 3.0
10.0
A function can have multiple variadic parameters. The first argument after a variadic parameter must have an argument label. The argument label is used to distinguish whether the argument is passed to the variadic parameter or the following parameter.
In-Out Parameters
Function parameters are constants by default. Attempting to change a parameter’s value inside the function body will result in a compile-time error. This prevents accidental modification of parameter values. If you want a function to be able to modify a parameter’s value and have those changes persist after the function call, you need to define the parameter as an in-out parameter.
To define an in-out parameter, add the inout
keyword before the parameter definition. An in-out parameter has a value passed into the function, is modified by the function, and then is passed out of the function, replacing the original value. For more details about in-out parameters and related compiler optimizations, see the section on in-out parameters.
You can only pass variables to in-out parameters, not constants or literals, because those cannot be modified. When passing a parameter as an in-out parameter, you need to prefix the parameter name with &
, indicating that the value can be modified by the function.
Note
In-out parameters cannot have default values, and variadic parameters cannot be marked as inout.
In the example below, the swapTwoInts(_:_:)
function has two in-out parameters named a
and b
:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
The swapTwoInts(_:_:)
function simply swaps the values of a
and b
. It first stores the value of a
in a temporary constant temporaryA
, then assigns the value of b
to a
, and finally assigns temporaryA
to b
.
You can call swapTwoInts(_:_:)
with two Int
variables. Note that both someInt
and anotherInt
are prefixed with &
before being passed to the function.
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("SomeInt is now \(someInt), and anotherInt is now \(anotherInt)!") // Prints "SomeInt is now 107, and anotherInt is now 3!"
---
output: SomeInt is now 107, and anotherInt is now 3!
As you can see from the example above, the original values of someInt
and anotherInt
are modified inside the swapTwoInts(_:_:)
function, even though they are defined outside the function body.
Note
In-out parameters are different from return values. The
swapTwoInts
function above does not define any return value, but still modifies the values ofsomeInt
andanotherInt
. In-out parameters are another way for a function to have an effect outside its body.
Function Types
Every function has a specific function type, which is composed of the function’s parameter types and return type. For example:
func addTwoInts(_ a: Int, _ b: Int) -> Int {
a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
a * b
}
This example defines two simple math functions: addTwoInts
and multiplyTwoInts
. Both functions take two Int
values and return an Int
value.
The type of these two functions is (Int, Int) -> Int
, which can be interpreted as:
- “This function type has two parameters of type Int and returns a value of type Int.”
Here is another example, a function with no parameters and no return value:
func printHelloWorld() {
print("Hello, world!")
}
The type of this function is () -> Void
, or “a function with no parameters that returns Void.”
Using Function Types
In Swift, you use function types just like other types. For example, you can define a constant or variable of a function type and assign an appropriate function to it.
var mathFunc: (Int, Int) -> Int = addTwoInts
This code can be interpreted as:
- “Define a variable called mathFunc of type ‘a function with two Int parameters that returns an Int’, and assign it to the addTwoInts function.”
Since addTwoInts
and mathFunc
have the same type, this assignment is allowed by Swift’s type checker.
Now you can use mathFunc
to call the assigned function:
print("Result: \(mathFunc(2, 3)).") // Prints "Result: 5."
---
output: Result: 5.
Different functions with matching types can be assigned to the same variable, just like non-function types:
mathFunc = multiplyTwoInts
print("Result: \(mathFunc(2, 3)).") // Prints "Result: 6."
---
output: Result: 6.
As with other types, when assigning a function to a constant or variable, you can let Swift infer its function type:
let anotherMathFunc = addTwoInts
// anotherMathFunc is inferred to be of type (Int, Int) -> Int
Function Types as Parameter Types
You can use a function type like (Int, Int) -> Int
as the parameter type of another function, allowing part of the function’s implementation to be provided by the caller:
func printMathResult(_ mathFunc: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(multiplyTwoInts(a, b)). ")
}
printMathResult(mathFunc, 8, 2) // Prints "Result: 16."
---
output: Result: 16.
This example defines a function printMathResult(_:_:_:)
with three parameters: the first parameter, mathFunc
, is of type (Int, Int) -> Int
and can accept any function of that type; the second and third parameters, a
and b
, are both Int
values to be passed as input to the given function.
When printMathResult(_:_:_:)
is called, it is passed the multiplyTwoInts
function and the integers 8 and 2. It calls multiplyTwoInts
with 8 and 2 and prints the result, 16.
The purpose of the printMathResult(_:_:_:)
function is to print the result of calling another math function of the appropriate type. It does not care how the passed-in function is implemented, only that it is of the correct type, allowing printMathResult(_:_:_:)
to delegate part of its functionality to the caller in a type-safe way.
Function Types as Return Types
You can use a function type as the return type of another function by writing a complete function type after the return arrow ->
.
The following example defines two simple functions, stepForward(_:)
and stepBackward(_:)
. The stepForward(_:)
function returns a value one greater than its input, and stepBackward(_:)
returns a value one less than its input. Both functions have the type (Int) -> Int
:
func stepForward(_ input: Int) -> Int {
input + 1
}
func stepBackward(_ input: Int) -> Int {
input - 1
}
The following function, chooseStepFunc(backward:)
, has a return type of (Int) -> Int
. It returns either the stepForward(_:)
or stepBackward(_:)
function based on the Boolean value backward
:
func chooseStepFunc(backward: Bool) -> (Int) -> Int {
backward ? stepBackward : stepForward
}
Now you can use chooseStepFunc(backward:)
to get one of the two functions:
var currentValue = 3
let moveNearerToZero = chooseStepFunc(backward: currentValue > 0)
In the example above, it is determined whether currentValue
should move toward zero by increasing or decreasing. The initial value of currentValue
is 3, so currentValue > 0
is true, causing chooseStepFunc(backward:)
to return the stepBackward(_:)
function. A reference to the returned function is stored in the constant moveNearerToZero
.
Now that moveNearerToZero
points to the correct function, it can be used to count down to zero:
print("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
print("\(currentValue)...")
currentValue = moveNearerToZero(currentValue)
}
print("Zero!") // 3... // 2... // 1... // Zero!
---
output: Counting to zero:
3...
2...
1...
Zero!
Nested Functions
So far, all the functions you have seen in this chapter are called global functions, defined in the global scope. You can also define functions inside other function bodies, called nested functions.
By default, nested functions are not visible to the outside world, but can be called by their enclosing function. An enclosing function can also return one of its nested functions, making it available in other scopes.
You can rewrite the chooseStepFunc(backward:)
function using a nested function (chooseStepFunction()):
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backward ? stepBackward(input:) : stepForward(input:)
}
var CurrentValue = -4
let MoveNearerToZero = chooseStepFunction(backward: currentValue > 0) // moveNearerToZero now refers to the nested stepForward() function
while CurrentValue != 0 {
print("\(CurrentValue)...")
CurrentValue = MoveNearerToZero(CurrentValue)
}
print("Zero!") // -4... // -3... // -2... // -1... // Zero!
---
output: -4...
-3...
-2...
-1...
Zero!