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