Functions are self-contained blocks of code that perform a specific task. Given a function name identifier, this identifier can be used to make a "call" when executing its task.
Swift's unified functional syntax is flexible enough to express anything, whether it's a simple C-style function expression without even a parameter name, or an Objective-C-style function that requires complex names for each local and external parameter. Parameters provide default values to simplify function calls and modify the passed variables when the function execution is completed by setting the input and output parameters.
Each function in Swift has a type, including the function's parameter type and return type. You can conveniently use this type like any other type, which makes it easy to pass a function as an argument to other functions, or even return the function type from the function. Functions can also be written in other functions to encapsulate a nested function for useful functions within a scope.
1. Declaration and call of function
When you define a function, you can define one or more different names and type values as inputs to the function (called parameters), and when the function completes, it will be passed back to the output defined type (called as its return type).
Each function has a function name that describes the task executed by the function. When using a function's function, you match the function's parameter type by using its name and by its input value (called a parameter). The provided parameters of a function must always be used as the function parameter list in the same order.
For example, the function greetingForPerson called in the example below, as it describes - it takes a person's name as input and returns a greeting to that person.
func sayHello(personName: String) -> String {
let greeting = "Hello, " + personName + "!"
return greeting
}
All this information is summarized into the function definition and prefixed with the func keyword. The return type of the function you specify is returned with the arrow -> (a hyphen followed by a right angle bracket) and the name of the subsequent type.
This definition describes what a function does, what it expects to receive, and what the result it returns when it completes. This definition makes it easy for the function to allow you to call it in a clear and explicit way elsewhere in the code:
println(sayHello("Anna"))
// prints "Hello, Anna!"
println(sayHello("Brian"))
// prints "Hello, Brian!"
Call the function of sayHello by using the String type parameter value in brackets, such as sayHello("Anna"). Since the function returns a string value, sayHello can be wrapped in a println function call to print the string and see its return value, as shown in the figure above.
In the function body of sayHello, a new String constant named greeting is defined, and the setting is added to the personal name of the personName to form a simple greeting message. Then the greeting function is returned with the keyword return. As long as the greeting function is called, the current value of the greeting will be returned after the function is executed.
You can call the sayHello function multiple times with different input values. The above example shows what happens if it takes "Anna" as the input value and "Brian" as the input value. The return of the function is a custom greeting in each case.
In order to simplify the body of this function, a line is expressed in combination with message creation and return statement:
func sayHello(personName: String) -> String {
return "Hello again, " + personName + "!"
}
println(sayHello("Anna"))
// prints "Hello again, Anna!"
2. Function parameters and return value
In swift, the parameters and return values of functions are very flexible. You can define anything whether it is a simple function with just one unnamed parameter or a complex function with rich parameter names and different parameter options.
Multiple input parameters
Functions can have multiple input parameters, write them into parentheses of the function, and separate them with commas. The following function sets a half-open interval of the start and end index to calculate how many elements are contained in the range:
func halfOpenRangeLength(start: Int, end: Int) -> Int {
return end - start
}
println(halfOpenRangeLength(1, 10))
// prints "9"
No parameter function
The function does not require input parameters that must be defined. Here is a function without input parameters. It always returns the same string message when called:
func sayHelloWorld() -> String {
return "hello, world"
}
println(sayHelloWorld())
// prints "hello, world"
The definition of the function also requires brackets after the function's name, even if it does not take any arguments. When the function is called, the function name must also be followed by a pair of empty brackets.
Functions with no return value
Functions also do not need to define a return type. Here is a version of the sayHello function called waveGoodbye, which outputs its own string value instead of the function returns:
func sayGoodbye(personName: String) {
println("Goodbye, \(personName)!")
}
sayGoodbye("Dave")
// prints "Goodbye, Dave!"
Since it does not need to return a value, the definition of the function does not include the return arrow (->) and the return type.
hint
Strictly speaking, the sayGoodbye function does return a value, even if there is no return value definition. The function does not define the return type but returns
Returns a special value of the void return type. It is a tuple that is simply empty, and actually has zero elements, which can be written as ().
When a function is called, its return value is negligible:
func printAndCount(stringToPrint: String) -> Int {
println(stringToPrint)
return countElements(stringToPrint)
}
func printWithoutCounting(stringToPrint: String) {
printAndCount(stringToPrint)
}
printAndCount("hello, world")
// prints "hello, world" and returns a value of 12
printWithoutCounting("hello, world")
// prints "hello, world" but does not return a value
The first function printAndCount prints a string, and returns its number of characters as Int type. The second function printWithoutCounting, the first function is called, but its return value is ignored. When the second function is called, the string message is printed back by the first function, and its return value is not used.
hint
The return value can be ignored, but for a function, its return value will definitely be returned even if it is not used. At the bottom of the function body
When returning a function that is not compatible with the defined return type, attempting to do so will result in a compile-time error.
Multiple return value function
You can use a tuple type as the return type of the function to return a complex cooperation consisting of multiple values as the return value.
The following example defines a function called count, which calculates the number of vowels, consonants, and characters used in a string based on standard American English:
func count(string: String) -> (vowels: Int, consonants: Int, others: Int) {
var vowels = 0, consonants = 0, others = 0
for character in string {
switch String(character).lowercaseString {
case "a", "e", "i", "o", "u":
++vowels
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
++consonants
default:
++others
}
}
return (vowels, consonants, others)
}
You can use this count function to character counting any string and retrieve tuples that count the total of three specified Int values:
let total = count("some arbitrary string!")
println("\() vowels and \() consonants")
// prints "6 vowels and 13 consonants"
It should be noted that at this point the members of the tuple do not need to be named in the tuple returned by the function, because their names have been specified as part of the return type of the function.
3. Function parameter name
All the above functions define parameter names for the parameters:
func someFunction(parameterName: Int) {
// function body goes here, and can use parameterName
// to refer to the argument value for that parameter
}
However, these parameter names can only be used in the body of the function itself and cannot be used when calling the function. These types of parameter names are called local parameters because they are only applicable to function bodies.
External parameter name
Sometimes it is very useful when you call a function to name each parameter to indicate the purpose of each parameter you pass to the function.
If you want the user function to provide parameter names when calling your function, in addition to setting the local parameter name, you should also define external parameter names for each parameter. You write an external parameter name to separate it with a space before the local parameter name it supports:
func someFunction(externalParameterName localParameterName: Int) {
// function body goes here, and can use localParameterName
// to refer to the argument value for that parameter
}
Notice
If you provide an external parameter name for the parameter, the external name must always be used when the function is called.
As an example, consider the following function, which concatenates two strings by inserting the third "joiner" string between them:
func join(s1: String, s2: String, joiner: String) -> String {
return s1 + joiner + s2
}
When you call this function, the purpose of the three strings you pass to the function is not very clear:
join("hello", "world", ", ")
// returns "hello, world"
To make the purpose of these string values clearer, define external parameter names for each join function parameter:
func join(string s1: String, toString s2: String, withJoiner joiner: String)
-> String {
return s1 + joiner + s2
}
In this version of the join function, the first parameter has an external name string and a local name s1; the second parameter has an external name toString and a local name s2; the third parameter has an external name withJoiner and a local name joiner.
Now you can call the function clearly and explicitly with these external parameter names:
join(string: "hello", toString: "world", withJoiner: ", ")
// returns "hello, world"
The use of external parameter names makes the second version of the join function more expressive. Users are used to using sentence-like methods, and also provide a readable and explicit function body.
Notice
Considering that the original intention of using external parameter names is to not know what the purpose of your function parameters is when others first read your code.
But when the function is called, if the purpose of each parameter is explicit and unambiguous, you do not need to specify the external parameter name.
External parameter name shorthand
If you want to provide an external parameter name for a function parameter, but the local parameter name has already used a suitable name, you do not need to write the same name twice for the parameter. Instead, write the name once and use a hash symbol (#) as the prefix of the name. This tells Swift to use that name as both the local parameter name and the external parameter name.
This example defines a function called containsCharacter, which defines the external parameter names of two parameters and precedes their local parameter names by placing a hash flag:
func containsCharacter(#string: String, #characterToFind: Character) -> Bool {
for character in string {
if character == characterToFind {
return true
}
}
return false
}
The parameter names selected by this function are clear and the function body is very readable, so that the function is called without ambiguity:
let containsAVee = containsCharacter(string: "aardvark", characterToFind: "v")
// containsAVee equals true, because "aardvark" contains a "v"
Default value of parameters
Default values can be set for any parameter as part of the definition of the function. If the default value is already defined, the value of the parameter can be omitted when calling the function.
Notice
Place the arguments that use the default value at the end of the function's parameter list. This ensures that non-default parameters of all calling functions use the same
order, and explicitly represent the same function call in each case.
There is a version here, which is an early join function and sets the default value for the parameter joiner:
func join(string s1: String, toString s2: String,
withJoiner joiner: String = " ") -> String {
return s1 + joiner + s2
}
If a string value is provided to the joiner when the join function is called, the string is used to concatenate two strings, just like before:
join(string: "hello", toString: "world", withJoiner: "-")
// returns "hello-world"
However, if the joiner's no value is provided when the function is called, the default value of a single space ("") is used:
join(string: "hello", toString: "world")
// returns "hello world"
External name parameter with default value
In most cases, it is very useful to provide all parameters with an external parameter with a default value (hence required). This will make sure if the parameter must have a clear purpose when the value provided when the function is called.
To make this process easier, Swift automatically defines the default parameter external name for all parameters when you do not provide an external name yourself. Automatic external names are the same as local names, as if you wrote a hash symbol before the local name in your code.
Here is an early version of the join function that does not provide external names for any arguments, but still provides the default value for the joiner argument:
func join(s1: String, s2: String, joiner: String = " ") -> String {
return s1 + joiner + s2
}
In this case, Swift automatically provides an external parameter name for a parameter with a default value. When calling a function, in order to make the purpose of the parameters clear and unambiguous, an external name must be provided:
join("hello", "world", joiner: "-")
// returns "hello-world"
Notice
You can choose to do this behavior by writing an underscore (_), rather than explicitly defining the external parameter name. Of course
However, external name parameters with default values are always used first in the appropriate situation.
Variable parameters
A variable parameter accepts zero or more values of the specified type. When a function is called, you can use a parameter of a mutable parameter to specify that the parameter can pass a different number of input values. When writing parameters of variable parameters, you need to add dot characters (…) to the type name of the parameter.
When passing the value of a variable parameter, the function body exists in the form of an array of appropriate types. For example, a mutable parameter has a name of numbers and a type of Double... in the function body it is used as an array of constants named numbers and a type of Double[].
The following example calculates the arithmetic mean (also called the average) of a number of any length:
func arithmeticMean(numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double()
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8, 19)
// returns 10.0, which is the arithmetic mean of these three numbers
Notice
The function can have at most one variable parameter parameter, and it must appear at the end of the parameter list to avoid multi-parameter functions
Ambiguity occurs when calling the number.
If the function has one or more parameters using the default value and also has variable parameters, place the variable parameters in the list
After all the default values at the end.
Constant parameters and variable parameters
The default values of function parameters are constants. Trying to change the value of a function parameter will cause a compile-time error inside the function body. This means you cannot change the value of the parameter incorrectly.
However, it is sometimes useful to have a copy of the variables with the value of a parameter. Instead of avoiding defining a new variable for yourself inside the function, you can specify one or more parameters as variable parameters. Variable parameters can be variables rather than constants, and provide a copy of the values of newly modified parameters in the function.
Define variable parameters with the keyword var before the parameter name:
func alignRight(var string: String, count: Int, pad: Character) -> String {
let amountToPad = count - countElements(string)
for _ in 1...amountToPad {
string = pad + string
}
return string
}
let originalString = "hello"
let paddedString = alignRight(originalString, 10, "-")
// paddedString is equal to "-----hello"
// originalString is still equal to "hello"
This example defines a new function called alignRight, which aligns an input string to a longer output string. Fill in the space on the left with specified characters. In this example, the string "hello" is converted to a string "--hello".
The alignRight function defines the string of the input parameter as a variable parameter. This means that the string can now be initialized as a local variable with the passed string value and can be operated accordingly in the function body.
The function first finds out how many characters need to be added to the left to allow the string to be right-aligned throughout the string. This value is stored in the local constant amountToPad. The function then copies the amountToPad characters of the fill character to the left of the existing string and returns the result. The entire process uses string variable parameters for string operations.
Notice
The change in a variable parameter does not exceed each call function, so it is invisible to the external function body. Variable parameters can only exist in function calls
During the life cycle.
Input-output parameters
Variable parameters, as mentioned above, can only be changed within the function itself. If you want to have a function to modify the value of the parameter and want to keep these changes at the end of the function call, you can define the input-output parameter instead.
Use the inout keyword to indicate the input-output parameters by adding the inout keyword at the beginning of its parameter definition. A input-output parameter has a value passed to the function, modified by the function, and returned from the function to replace the original value.
4. Function type
Each function has a specific type, including parameter type and return value type, such as:
func addTwoInts(a: Int, b: Int) -> Int {
return a + b
}
func multiplyTwoInts(a: Int, b: Int) -> Int {
return a * b
}
This example defines two simple mathematical functions addTwoInts and multiplyTwoInts. Each function accepts two int parameters, returns an int value, performs the corresponding mathematical operation and returns the result
The types of these two functions are (Int, Int)->Int can be interpreted as:
This function type has two int-type parameters and returns a value of int-type
The following example is a function without any parameters and return values:
func printHelloWorld() {
println("hello, world")
}
The type of this function is ()->(), or the function has no parameters, and returns void. The function does not explicitly specify the return type, and defaults to void, which is equivalent to an empty tuple in Swift, and is denoted as ().
Using function types
In swift you can use function types like any other type. For example, you can define a constant or variable as a function type and specify the appropriate function to the variable:
var mathFunction: (Int, Int) -> Int = addTwoInts
It can be interpreted as:
"Define a variable named mathFunction, which has a type of 'a function that accepts two int values and returns an int value.' Set this new variable to reference a function named addTwoInts."
The mathFunction function has the same type variable as the addTwoInts function, so this assignment can pass Swift's type check.
Now you can call the specified function name as mathFunction:
println("Result: \(mathFunction(2, 3))")
// prints "Result: 5"
Different functions have the same matching types that can be assigned to the same variables, and they are also applicable to non-functional types:
mathFunction = multiplyTwoInts
println("Result: \(mathFunction(2, 3))")
// prints "Result: 6"
Like other types, you can quickly define it as a function type when you assign a function to a constant or variable:
let anotherMathFunction = addTwoInts
// anotherMathFunction is inferred to be of type (Int, Int) -> Int
Function type parameters
You can use a function type, such as (Int, Int)->Int as the parameter type of another function. This allows you to reserve some aspects of a function implementation that allows the caller to provide when calling the function.
Let’s take the result of printing the above mathematical function as an example:
func printMathResult(mathFunction: (Int, Int) -> Int, a: Int, b: Int) {
println("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// prints "Result: 8"
In this example, a function called printMathResult is defined, which has three parameters. The first parameter is named mathFunction and has type (Int, Int)->Int. You can pass in any function type that meets the criteria as the first parameter of this function. The second and third parameters a and b are both int types. Used as two input values for providing mathematical functions.
When printMathResult is called, it passes the addTwoInt function, along with integer values 3 and 5. It uses 3 and 5, calls the addTwoInt function, and prints the result of the function running 8.
The purpose of printMathResult is to call a mathematical function of the appropriate type and print the corresponding result. It doesn't matter what function it is, you just need to match the correct type. This makes printMathResult convert the function's functionality in a caller-type-safe way.
Return value of function type
One function type can be used as the return type of another function. The returned function (->) is the return arrow, immediately write a complete function type to do this.
The following example defines two simple functions, stepForward and stepBackward. The stepForward function returns the input value to increase by 1, while the stepBackward function returns the input value to decrease by 1. Both functions have the same type (Int) -> Int:
func stepForward(input: Int) -> Int {
return input + 1
}
func stepBackward(input: Int) -> Int {
return input - 1
}
Here is a choiceStepFunction function, and its return type is "Function type (Int) -> Int". chooseStepFunction returns a stepBackward or stepForward function type based on a boolean parameter:
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
return backwards ? stepBackward : stepForward
}
You can now select a function using a choiceStepFunction, maybe adding a function or another:
var currentValue = 3
let moveNearerToZero = chooseStepFunction(currentValue > 0)
// moveNearerToZero now refers to the stepBackward() function
The above example can determine whether the positive and negative decision of the step needs to be moved so that the currentValue variable gradually approaches zero. The initial value of currentValue is 3, which means the current value is > 0, then true is returned, and chooseStepFunction returns the stepBackward function. The reference to the return function is stored in a constant called moveNearerToZero.
Now that moveNearerToZero performs the correct function, it can be used to count to zero:
println("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
println("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
println("zero!")
// 3...
// 2...
// 1...
// zero!
5. Nested functions
All functions you have encountered so far in this chapter are global functions, defined in the global scope. In fact, you can also define functions in other functions, called nested functions.
Nested functions are hidden from the outside world by default, but they can still be called and used within them. An internal function can also return a nested function, allowing it to be used within another scope within a nested function.
You can override the above choiceStepFunction example using and return nested functions:
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backwards ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
println("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
println("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!