Closures are independent blocks of code that can be passed around and used in code at will. Closures in Swift are similar to blocks in Objective-C/C, and anonymous functions in other programming languages.
Global and nested functions are actually special closures. Closures take one of three forms:
- A global function is a closure that has a name but does not capture any values.
- A nested function is a closure that has a name and can capture values in its enclosing function field.
- A closure expression is an anonymous closure written in lightweight syntax that captures the value of a variable or constant in its context.
Global function | Nested function | Closure expression |
---|---|---|
Name but does not capture any values | Closure that has a name and can capture values in its enclosing function field | An anonymous closure written in lightweight syntax that captures the value of a variable or constant in its context |
Closure expression
grammar
The standard syntax for closure expressions in Swift is as follows: {(parameter list) -> Return value type in function body code} Where, the argument list is the same as the parameter list in function, the return value type is similar to the return value type in function, but the difference is that the in keyword (split the return value type and function body code).
Example:
var sum = {
(v1: Int, v2: Int) -> Int in
return v1 + v2
}
sum(1.2) output:3
Copy the code
As you can see, the closure expression above is very similar to the function defined by func, and does the same thing
func sumFunc(v1: Int, v2: Int) -> Int {
return v1 + v2
}
sumFunc(v1: 1, v2: 2)
Copy the code
Swift, the abbreviation for closure expression, provides a variety of closure shorthand ways to define a function that takes three arguments, where the third argument is a function type, calls the third argument (the function passed in) within the function, and evaluates the sum of the two arguments passed in
func sumFunc(v1: Int, v2: Int, sum: (Int, Int) -> Int) {
print(sum(v1, v2))
}
Copy the code
Call sumFunc, passing in the third argument, passed in through the standard closure expression
sumFunc(v1: 1, v2: 2, sum: {
(number1: Int, number2: Int) - >Int in
return number1 +Number2}) Final print:3
Copy the code
Type inference simplification can be simplified because the sum argument of a parameter can be inferred to be an Int when the closure expression argument is passed to a call function
sumFunc(v1: 1, v2: 2, sum: {
number1, number2 in
return number1 +Number2}) output:3
Copy the code
Hidden return keyword (single-expression closures implicitly return) If there is only one statement in the closure’s internal statement group, such as return A + b, then these statements are return statements. {number1, number1 in number1 + number1}}
sumFunc(v1: 1, v2: 2, sum: {
number1, number2 in
number1 +Number2}) output:3
Copy the code
Note that the omission assumes that there is only one return statement in the closure: number1 + number2. Several of the following statements are not allowed.
Abbreviation parameter name Swift provides the function of the parameter name abbreviations, we can use 0, 0, 0, 1, 2, to represent the call closure parameters, 2 to indicate the parameters in the call closure, 2 to indicate the call closure parameters, refer to the first argument, 0 1 refers to the second argument, 1 refers to the second argument, 1 refers to the second argument, 2 refers to the third parameter, and so on, n+1 refers to the NTH parameter. Using parameter name abbreviations, and optionally omitting the definition of the parameter list from the closure, Swift can infer the type of these abbreviated parameters. Also, the in keyword can be omitted. The parameter names are abbreviated as follows: {0 + $1}
sumFunc(v1: 1, v2: 2, sum: { $0 + The $1}) output:3
Copy the code
The operator methods actually have a shorter way of writing the closure expression in the example above. The + of Swift Int can be directly abbreviated to +.
sumFunc(v1: 1, v2: 2, sum: +) output:3
Copy the code
Following the closure
If you take a long closure expression as the last argument to a function, you can use trailing closures to improve the readability of the function. A trailing closure is a closure expression written after the function parentheses, which the function supports calling as the last argument. When you use a trailing closure, you don’t have to write out its parameter labels
FuncClosure (closure: () -> Void) {// funcClosure(closure: () -> Void) {// funcClosure(closure: () -> Void) {// funcClosure(closure: FuncClosure () {// Closure body}) funcClosure() {// Closure body}Copy the code
Also take the sumFunc example above:
sumFunc(v1: 1, v2: 2) {
$0 + $1
}
Copy the code
If the closure expression is the only argument to a function or method, then you can even omit () when using trailing closures:
func addFunc(add: (Int, Int) -> Int) {
print(add(1, 2))
}
addFunc {
$0 + $1
}
Copy the code
Closure value capture
Closures can capture constants or variables in the context in which they are defined. Even if the original scope that defined these constants and variables no longer exists, closures can still reference and modify these values inside the closure function.
In Swift, the simplest form of a closure that can capture values is a nested function, that is, a function defined within the body of another function. A nested function can capture all the parameters of its external function as well as the constants and variables defined.
The example function summationFunc (an argument that returns a function) has a nested function summation (no argument, returns an integer). The nested function summation() captures two values, number and result. After capturing these two values, the outer function summationFunc returns the nested function summation as a closure. Each time a summation is called, it increments to result by number.
func summationFunc(number: Int) -> () -> Int {
var result = 2
func summation() -> Int {
result += number
return result
}
return summation
}
let function = summationFunc(number: 10)
let v1 = function()
let v2 = function()
let v3 = function()
print("v1=\(v1), v2=\(v2), v3=\(v3)")
let function2 = summationFunc(number: 10)
let v4 = function2()
let v5 = function2()
let v6 = function2()
print("v4=\(v4), v5=\(v5), v6=\(v6)")
Copy the code
Output information:
v1=12, v2=22, v3=32
v4=12, v5=22, v6=32
Copy the code
You can see that the program works fine, and function and function2 accumulate separately with each call. We know that in general function parameters and local variables in the function are stored on the stack and destroyed at the end of the call. The function here, summationFunc, is not what we expected. For nested functions:
func summation() -> Int {
result += number
return result
}
Copy the code
The function has no arguments, but the result and number variables are called inside the function. This is because it captures references to the result and number variables. Capturing the reference ensures that the Result and number variables do not disappear after a call to summationFunc, and that the variables are still present the next time the summation function is executed.
Breakpoint debugging shows that the closure calls swift_allocObject, creating an object to store in the heap, that is, creating an object in the heap to store the result. The exact number of closures should be described as a combination of functions with the variables (or constants) they capture.
When to capture context variables (constants) :
func summationFunc(number: Int) -> () -> Int {
var result = 2
func summation() -> Int {
result += number
return result
}
result = 200
return summation
}
let function = summationFunc(number: 10)
let v1 = function()
let v2 = function()
print("v1=\(v1), v2=\(v2)")
Copy the code
Output:
v1=30, v2=40
Copy the code
Debug can see that it is captured twice (once for each assignment) before returning, and that it ends up storing the value of the last assignment
Closures are reference types
In the above example, function and function1 are constants, but the closures to which these constants point can still increase the value of the variables they capture. This is because functions and closures are reference types.
Whether you assign a function or closure to a constant or variable, you are actually setting the constant or variable’s value as a reference to the corresponding function or closure. In the example above, the reference to the closure function (or function1) is a constant, not the closure content itself. This also means that if you assign a closure to two different constants or variables, both values will refer to the same closure: for example, if you assign the above function to testFunction, the call continues
func summationFunc(number: Int) -> () -> Int {
var result = 2
func summation() -> Int {
result += number
return result
}
return summation
}
let function = summationFunc(number: 10)
let v1 = function()
let v2 = function()
let v3 = function()
print("v1=\(v1), v2=\(v2), v3=\(v3)")
let testFunction = function
let t1 = function()
let t2 = function()
let t3 = function()
print("t1=\(t1), t2=\(t2), t3=\(t3)")
Copy the code
Print information:
v1=12, v2=22, v3=32
t1=42, t2=52, t3=62
Copy the code