A closure is a self-contained function code block that can be passed and used in the code. Closures in swift are similar to code blocks (blocks) in C and objective-C, and Lambdas functions in other programming languages.
Closures can capture and store references to any constants and variables in their context. This is the so-called closure and enclose these constants and variables, commonly known as closures. Swift manages all memory operations involved in the capture process.
Note:
If you are not familiar with the concept of capturing, you can learn more about it in the Value capture chapter.
The global and nested functions described in the function section are actually special closures. Closures take one of the following three forms:
A global function is a closure with a name but no value is captured.
A nested function is a closure with a name and can capture the values in the function domain.
A closure expression is an anonymous closure that uses lightweight syntax to capture variables or constant values in its context.
The closure expression of SWIFT has a concise style and encourages syntax Optimization in common scenarios. The main optimization is as follows:
Use context inference parameters and return value types
The closure of a single expression is returned implicitly, that is, the closure of a single expression can omit the return keyword.
Abbreviated parameter name
Trailing closure syntax
Closure expression (Closure expressions)
Nested functions are convenient in naming and defining self-contained code modules in complex functions. Of course, sometimes it is very useful to write a small class function structure without complete definition and naming, especially when you process some functions and need to use other functions as parameters of the function.
Closure expressions are a method of constructing inline closures using concise syntax. The closure expression provides some syntax Optimizations to make it easy and clear to write the closure. The example of the closure expression below demonstrates the sort function definition and syntax optimization through several iterations. Each iteration describes the same function in a more concise way.
The sort function)
The swift Standard Library provides the sort function, which sorts the values in an array of known types based on the closure function of the output type you provide. Once sorting is completed, the function returns a new array of the same size as the original array, which contains the same type of elements that have been correctly sorted.
The following closure expression uses the sort function to sort the letters of a string array in reverse order. The following are the initial array values:
Let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
The sort function requires two parameters:
Array of known types
Closure function, which requires two values of the same type as the array, return a Boolean value to tell the sort function that the first parameter passed in after sorting is placed before or after the second parameter. If the value of the first parameter appears before the value of the second parameter, the sort closure function must return true. Otherwise, false is returned.
This example sorts an array of the string type. Therefore, the type of the sort closure function must be (string, string)-> bool.
One way to provide the sort closure function is to write a common function that meets its type requirements and pass it as the second parameter of the sort function:
func backwards (s1: String, s2: String)-> Bool {
return s1> s2
}
var reversed = sort (names, backwards)
// reversed is ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
If the first string (s1) is greater than the second string (s2), the backwards function returns true, indicating that s1 should appear before s2 in the new array. For the characters in the string, "greater than" means "appear later in alphabetical order". This means that the letter "B" is greater than the letter "A", and the string "Tom" is greater than the string "Tim". It will be sorted in reverse alphabetical order, and "Barry" will be ranked after "Alex".
However, this is a rather verbose way, essentially just writing a single expression function (a> b). In the following example, the use of closed expression syntax can better construct an inline sort closure.
Closure Expression Syntax
The closure expression syntax has the following general form:
{(parameters)-> returnType in
statements
}
Closure expression syntax can use constants, variables, and inout types as parameters, and does not provide default values. Variable parameters can also be used at the end of the parameter list. Tuples can also be used as parameters and return values.
The following example shows the code of the closure expression version corresponding to the previous backwards function:
reversed = sort (names, {(s1: String, s2: String)-> Bool in
return s1> s2
})
It should be noted that the inline closure parameter and return value type declarations are the same as the backwards function type declarations. In these two ways, they are written as (s1: String, s2: String)-> Bool. However, in inline closure expressions, the function and return value types are written in braces, not outside the braces.
The function body part of the closure is introduced by the keyword in. This keyword indicates that the parameter and return value types of the closure have been defined and the body of the closure function is about to begin.
Because the function body of this closure is so short that it can be rewritten into a single line of code:
reversed = sort (names, {(s1: String, s2: String)-> Bool in return s1> s2})
This shows that the overall call of the sort function remains unchanged, and a pair of parentheses still wraps the entire parameter set in the function. One of the parameters is now an inline closure (compared to the backwards version of the code).
Inferring type from context (Inferring Type From Context)
Because the sort closure function is passed in as a parameter of the sort function, Swift can infer the type of its parameters and return value. Sort expects the second parameter to be a function of type (String, String)-> Bool, so in fact String, String and Bool types do not need to be part of the definition of the closure expression. Because all types can be correctly inferred, the return arrow (->) and the parentheses around the parameter can also be omitted:
reversed = sort (names, {s1, s2 in returns1> s2})
In fact, in any case, when the closure constructed by the inline closure expression is passed to the function as a parameter, you can infer the parameter and return value type of the closure, which means that you rarely need to construct any inner using the complete format Joint closure.
Implicit Return From Single-Expression Clossures
A single-line expression closure can implicitly return the result of a single-line expression by hiding the return keyword. The example in the previous version can be rewritten as:
reversed = sort (names, {s1, s2 in s1> s2})
In this example, the second parameter function type of the sort function specifies that the closure must return a value of type Bool. Because the body of the closure function contains only a single expression (s1> s2), the expression returns a value of type Bool, so there is no ambiguity here, and the return keyword can be omitted.
Parameter name abbreviations (Shorthand Argument Names)
Swift automatically provides parameter name abbreviation for inline functions. You can directly call the parameters of the closure through $ 0, $ 1, $ 2.
If you use parameter name abbreviations in closure expressions, you can omit their definition in the closure parameter list, and the type of the corresponding parameter name abbreviation is inferred by the function type. The in keyword can also be omitted, because the closure expression is entirely composed of the closure function body:
reversed = sort (names, {$ 0> $ 1})
In this example, $ 0 and $ 1 represent the first and second String type parameters in the closure.
Operator Functions
There is actually a shorter way to write the closure expression in the above example. Swift's String type defines a string implementation about greater than (>), which accepts two String type parameters as a function and returns a Bool type value. And this just coincides with the type of function required for the second parameter of the sort function. Therefore, you can simply pass a greater-than sign, and Swift can automatically infer that you want to use the greater-than string function implementation:
reversed = sort (names,>)
For more information about operator expressions, please see Operator Functions.
Trailing Closures
If you need to pass a long closure expression as the last parameter to the function, you can use trailing closures to enhance the readability of the function. A trailing closure is a closure expression written after the parentheses of the function. The function supports calling it as the last parameter.
func someFunctionThatTakesAClosure (closure :()-> ()) {
// Function body part
}
// The following is a function call without a trailing closure
someFunctionThatTakesAClosure ({
// The main part of the closure
})
// The following is a function call using a trailing closure
someFunctionThatTakesAClosure () {
// The main part of the closure
}
note:
If the function only needs one parameter of the closure expression, when you use the trailing closure, you can even omit ().
In the above example, the string sort closure as a parameter of the sort function can be rewritten as:
reversed = sort (names) {$ 0> $ 1}
Trailing closures become very useful when closures are so long that they cannot be written on one line. For example, Swift's Array type has a map method, which takes a closure expression as its only parameter. Each element in the array calls the closure function once and returns the value mapped by the element (which can also be a different type of value). The specific mapping method and return value type are specified by the closure.
When provided to the array closure function, the map method will return a new array, which contains the mapped values corresponding to the original array.
The following example shows how to use a trailing closure in the map method to convert an Int type array [16,58,510] to an array containing the corresponding String type ["OneSix", "FiveEight", "FiveOneZero"]:
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
The above code creates an English version dictionary with digits mapped to their names. At the same time, an integer array to be converted to a string is defined.
You can now create a corresponding array of string versions by passing a trailing closure to the map method of numbers. When you need to pay attention, calling numbers.map does not need to include any parentheses after the map, because it only needs to pass the closure expression parameter, and the closure expression parameter is written in a trailing manner:
let strings = numbers.map {
(var number)-> String in
var output = ""
while number> 0 {
output = digitNames [number% 10]! + output
number / = 10
}
return output
}
// strings constants are inferred as arrays of string type, ie String []
// The value is ["OneSix", "FiveEight", "FiveOneZero"]
map calls the closure expression for each element in the array. You do not need to specify the type of the input parameter number of the closure, because it can be inferred by the type of the array to be mapped.
The number parameter of the closure is declared as a variable parameter (see the constant parameter and variable parameter for the specific description of the variable), so it can be modified in the body of the closure function. The closure expression specifies a return type of String to indicate that the new array type storing the mapped value is String.
The closure expression creates a string and returns each time it is called. It uses the remainder operator (number% 10) to calculate the last digit and uses the digitNames dictionary to get the mapped string.
note:
The dictionary digitNames subscript is followed by an exclamation point (!), Because the dictionary subscript returns an optional value, indicating that the search will not fail even if the key does not exist. In the above example, it ensures that number% 10 can always be used as a valid subscript key for a digitNames dictionary. Therefore, the exclamation point can be used to force-unwrap the String value stored in the optional subscript item.
The string obtained from the digitNames dictionary is added to the front of the output, and a string version of the number is created in reverse order. (In the expression number% 10, if the number is 16, it returns 6, 58 returns 8, and 510 returns 0).
The number variable is divided by 10. Because it is an integer, the undivided part is ignored during the calculation. So 16 becomes 1, 58 becomes 5, and 510 becomes 51.
The whole process is repeated until number / = 10 is 0, then the closure will output the string, and the map function will add the string to the mapped array.
In the above example, the trailing closure syntax neatly encapsulates the specific closure function after the function, instead of wrapping the entire closure in the parentheses of the map function.
Capturing Values
Closures can capture constants or variables in the context of their definition. Even if the original domain that defines these constants and variables no longer exists, the closure can still reference and modify these values in the body of the closure function.
The simplest form of closure in Swift is a nested function, which is a function defined in the body of another function. Nested functions can capture all the parameters and defined constants and variables of their external functions.
The following example is a function called makeIncrementor, which contains a nested function called incrementor. The nested function incrementor captures two values from the context, runningTotal and amount. Then makeIncrementor returns the incrementor as a closure. Each time incrementor is called, it increments the runningTotal value in increments.
func makeIncrementor (forIncrement amount: Int)-> ()-> Int {
var runningTotal = 0
func incrementor ()-> Int {
runningTotal + = amount
return runningTotal
}
return incrementor
}
The return type of makeIncrementor is ()-> Int. This means that it returns a function Instead of a simple type value. The function does not accept parameters every time it is called and only returns a value of type Int. For the content of functions returning other functions, please check the function type as the return type.
The makeIncrementor function defines an integer variable runningTotal (initially 0) to store the current running total. This value is returned by incrementor.
makeIncrementor has an Int type parameter, whose external name is forIncrement and internal name is amount, which means that the runningTotal will increase each time the incrementor is called.
The incrementor function is used to perform the actual increase operation. This function simply increases runningTotal by the amount and returns it.
If we look at this function alone, we will find that it looks unusual:
func incrementor ()-> Int {
runningTotal + = amount
return runningTotal
}
The incrementor function does not get any parameters, but the runningTotal and amount variables are accessed in the function body. This is because it is achieved by capturing the runningTotal and amount variables that already exist in the body containing the function.
Since the amount variable is not modified, the incrementor actually captures and stores a copy of the variable, and the copy is stored along with the incrementor.
However, because the value of runningTotal is modified each time the function is called, the incrementor captures the current runningTotal variable reference, rather than just copying the initial value of the variable. Capturing a reference guarantees that it will not disappear when makeIncrementor ends, and also guarantees that runningTotal can continue to increase when the incrementor function is executed next time.
note:
Swift will decide whether to capture the reference or copy the value. You don't need to label amount or runningTotal to declare the usage in the embedded incrementor function. Swift also handles the memory management operation of the runningTotal variable. If it is no longer used by the incrementor function, it will be cleared.
The following code is an example of using makeIncrementor:
let incrementByTen = makeIncrementor (forIncrement: 10)
This example defines a constant called incrementByTen, which points to an incrementor function that adds 10 to each call. Call this function multiple times to get the following results:
incrementByTen ()
// The returned value is 10
incrementByTen ()
// The returned value is 20
incrementByTen ()
// The returned value is 30
If you create another incrementor, it will have a reference to its own independent runningTotal variable. In the following example, incrementBySevne captures a new runningTotal variable, which has nothing to do with the variable captured in incrementByTen:
let incrementBySeven = makeIncrementor (forIncrement: 7)
incrementBySeven ()
// The returned value is 7
incrementByTen ()
// The returned value is 40
note:
If your closure is assigned to an attribute of a class instance, and the closure captures the instance by pointing to the instance or its members, you will create a strong reference ring between the closure and the instance. Swift uses a capture list to break this strong reference circle. For more information, please refer to the strong circular references caused by closures.
Closures are reference types (Closures Are Reference Types)
In the above example, incrementBySeven and incrementByTen are constants, but the closures pointed to by these constants can still increase the value of their captured variables. This is because functions and closures are reference types.
Whether you assign a function / closure to a constant or a variable, you are actually setting the value of the constant / variable to the reference of the corresponding function / closure. In the above example, the reference of incrementByTen to the closure is a constant, not the content of the closure itself.
This also means that if you assign closures to two different constants / variables, both values will point to the same closure:
let alsoIncrementByTen = incrementByTen
alsoIncrementByTen ()
// The returned value is 50