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