Welcome everyone to pay attention to the public account: good code farmers, pay attention to can get a variety of learning materials and classics, for everyone on the career path to help a force.

Closures are self-contained blocks of functional code, similar to code blocks in C and Objective-C and anonymous functions in other languages. Closures can be used as arguments to functions or as return values of functions. 3. It can be used for callback and reverse transmission as in OC.

Closure expressions

A closure expression can be understood as the closure’s representation syntax

{(parameter list) -> Return value type in function body code}Copy the code
  • Multiple parameters can be separated by a comma
  • The parentheses of the argument list can be omitted
  • The return value type can also be omitted
  • In can be omitted when there are no arguments
  • In can be thought of as a separator separating the function body from the preceding argument and return value

1. There are parameters and return values

// let testOne: (String,String) -> String = {(str1,str2) -> String in return str1 + str2 } print(testOne("test","One"))Copy the code

The right side of: is the type of closure, and the right side of = is the expression of a closure, which can also be understood as a closure. = The right-hand side is written exactly as the closure expression, with arguments, parentheses, and return values. Now let’s look at the shorthand for closure expressions

let testOne = {str1,str2 in
    return str1 + str2
}
print(testOne("test","One"))
Copy the code

This is equivalent to the previous one, in that the closure expression leaves out the parentheses and return values of the arguments. The closure type on the right side is omitted because the Swift compiler automatically determines the type from the = right side

2. No parameter, no return value

Let testThree: () -> Void = {print("testThree")} testThree()Copy the code

: The right type is omitted

let testThree = {
    print("testThree")
}
testThree()
Copy the code

Because there is no argument in that can be omitted

Closures as function arguments

func exec(fn: (Int, Int) -> Int, v1: Int, v2: Int) {
    print(fn(v1, v2))
}
Copy the code

This function takes three arguments. The first argument is a function followed by a call to the exec function

exec(fn: { a, b in
    return a + b
}, v1: 1, v2: 2)
Copy the code

When the exec function is called, {} is a closure expression that can be thought of as an implementation of the first argument function. This form of function call can seem unfriendly, and even more unfriendly if the closure expression has many lines, making the code unreadable. Swift provides the concept of a trailing closure

1. Trailing closures

  • When the last argument to a function is a function, the closure expression can be written outside () when the function is called
  • A trailing closure is a closure expression written after the function parentheses
  • If you take a long closure expression as the last argument to a function, using trailing closures can enhance the readability of the function
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))
}
Copy the code

Exec (fn); exec (fn); exec (fn)

exec(v1: 1, v2: 2) { a, b in
    return a + b
}
Copy the code

In contrast to the last function call, this time the closure expression is written after the function parentheses to improve the readability of the code. This is the trailing closure.

  • You can use simplified parameter names such as $0, $1(starting at 0 for the ith parameter…)
exec(v1: 1, v2: 2) {
    return $0 + $1
}
Copy the code

There are a lot of abbreviations that I don’t want to list, but too abbreviations are bad for code reading

  • If the closure expression is the only argument to a function and the trailing closure syntax is used, you can omit the parentheses following the function name
Func exec(fn: (Int, Int) -> Int) {print(fn(1,2))} exec {a, b in return a + b}Copy the code

2. Escape closure

  • If a closure is taken as an argument to a function and is executed after the function is finished executing, the closure in this case is called an escape closure
  • Escaping closures are qualified with @escaping after: of the argument name

When asynchronous operations are involved, closures are placed in asynchronous threads, which is where escape closures occur, especially for network requests

Func exec (fn: @ escaping () - > ()) {/ / 5 s DispatchQueue delay. Main. AsyncAfter (deadline: Dispatchtime.now () + 5) {print(" function done ")} exec {print(" function done ")}Copy the code

This code will print “function completed”, and then five seconds later it will print “closure completed”

3. Automatic closure

  • Use @autoclosure after: to describe automatic closures
  • @autoClosure will automatically wrap 20 into a closure {20}
  • @Autoclosure only supports arguments in () -> T format
  • @Autoclosure does not support only the last parameter
  • Function overloading with and without @Autoclosure
  • Null merge operator?? The @Autoclosure technique is used

So why do we have automatic closures, and what do we do with automatic closures

func getFirstPositive(_ a: Int, _ b: () -> Int) -> Int {
    return a > 0 ? a : b()
}

getFirstPositive(5) {
    return 20
}
Copy the code

The getFirstPositive function is called with a trailing closure, which can be written as an automatic closure if the argument is in the () -> T format, making the code easier to read

func getFirstPositive(_ a: Int, _ b: @autoclosure () -> Int) -> Int {
    return a > 0 ? a : b()
}

getFirstPositive(1, 10)
Copy the code

It is important to note that closures have deferred execution and are executed only when called from within the function

func getFirstPositive(_ a: Int, _ b: @autoclosure () -> Int) -> Int { return a > 0 ? Func exec() -> Int {print(" exec") return 20} getFirstPositive(1, exec())Copy the code

When looking at the getFirstPositive(1, exec()) function call, it is easy to mistakenly think that exec() has executed the function exec, when it has not. This is because closure execution is delayed. The exec function is not executed inside getFirstPositive because a>0 returns the value of A and is not called to b(). The exec function is executed only if b() is called inside getFirstPositive

Closures capture variables

  • Closures can capture variables \ constants of external functions
  • Closure capture is done when the function completes and returns
  • When there are multiple closures in a function, the variable \ constant is captured only once, and the captured variable \ constant is shared by multiple closures
  • Closures do not capture global variables

The following code illustrates these conclusions

//MARK: Typealias fn = (Int) -> () func exec() -> fn {var num = 0 return {a in num += a print(num)}} let fn1 = exec() fn1(1) fn1(2) fn1(3)Copy the code

The output results of Fn1, Fn2 and FN3 are 1, 3 and 6, respectively. This is a function that returns a closure in which num is accumulated and output.

When fn1 is called for the first time, num is 0,0 plus the argument 1=1

When fn1 is called for the second time, num in the closure is the summation result of the first fn1, 1,1 plus parameter 2=3. This is because the closure captures the external num and reallocates the memory on the heap. When executing let fn1 = exec(), the closure gives fn1 the memory address, so every call to fn1 is the same block of memory, the same closure, and the closure contains the memory address of the num captured in the save. So the same num is called every time

You can think of a closure as an instance object of a class with heap space in it, the captured local variables \ constants are the members of the object (storing properties), and the functions that make up the closure are the methods defined inside the class

typealias fn = (Int) -> ()
func exec() -> fn {
    var num = 0
    return {a in
        num += a
        print(num)
    }
}

let fn1 = exec()
fn1(1)
let fn2 = exec()
fn2(1)
Copy the code

Modify the above code slightly, assigning exec to fn1 and fn2, respectively, yields 1 and 1. Exec assigns the value to fn1 and fn2, respectively. Fn1 and fn2 refer to two different addresses, and num is initialized to 0 every time exec() is called

typealias fn = (Int) -> ()
func exec() -> fn {
    var num = 0
    func plus(a: Int) {
        num += a
        print(num)
    }
    num = 6

    return plus
}

let fn1 = exec()
fn1(1)
Copy the code

What is the output num of this code? The answer is 7

This is also a local variable capture problem. The closure will capture num only when the function has finished executing and returned. At this point, num has changed from 0 to 6, so fn1(1) will output 7

typealias fn = (Int) -> ()
func exec() -> (fn, fn) {
    var num = 0
    func plus(a: Int) {
        num += a
        print("plus:", num)
    }
    
    func minus(a: Int) {
        num -= a
        print("minus:", num)
    }

    return (plus, minus)
}

let (p, m) = exec()
p(5)
m(4)
Copy the code

This code function returns a meta-parent containing two closures, and num is called in both closures. The output is: plus: 5 minus: 1 because when a function has multiple closures, the variable \ constant is captured only once, and the captured variable \ constant is shared by multiple closures

Because when there are multiple closures in a function, the variable \ constant is captured only once, and the captured variable \ constant is shared by multiple closures. When m(4) is called, p(5) has already been called, and num has already changed to 5, so when m(4) is called, the output is 1.

Circular references in closures and solutions

You can check out his article on the simple use of closures in Swift, which he explains in detail

Refer to the article

Definition and use of Swift closures

Simple use of closures in Swift

Escape closure, non-escape closure