Closures are classified by their life cycle, in terms of how they are created, delivered, and invoked, into automatic closures @autoclosure, trailing closures, and escaping closures @escaping. Closures can be created, passed, and used in many simplified forms based on type inference. Automatic closures simplify the complexity of closure creation, but they are not recommended to be used too much. Trailing closures simplify the process of passing closures as parameters and greatly improve the writing format of code. Escape closures provide syntactic and semantic support for the closure to be invoked after the current function returns, and are a typical asynchronous mechanism.
When using a value type in a closure, consider whether capturing a variable is valid at runtime. If the original value on the stack changes, the value type captured in the closure will not be updated. Consider using an object substitution structure or enumeration, or wrapping it with objects.
Avoid introducing strong reference rings when using reference types and explicitly specify capture attributes using a capture list.
Closure overview
Closures are self-contained functional modules that capture and store references to constants and variables in context. Global and nested functions are special closures. Closures come in three forms:
- Global functions: closures that have names and do not capture any values;
- Nested functions: closures with names that can capture any value;
- Closure expressions: Nameless, catch-value, concise and efficient. The ability to infer the types of parameters and return values, the value of an implicit unique expression in a return closure, shorthand for parameter names, and tail-closure syntax.
Closure expression syntax
{ (parameters) -> return type in statements}
Copy the code
In is preceded by the parameter and return value type declarations, followed by the closure body.
This article mainly introduces closure expressions, function closures refer to docs.swift.org/swift-book/…
Closure expression
Using nested functions in large functions makes it very easy to define closures. But in some cases, a shorter version of a function-like construction block is more useful, with no name and many declared elements ignored.
Closure expressions are one such way of creating inline closures, and they provide several syntactic optimizations (see point 3).
Closure as function argument
The sorted(by:) method provided by the library sorts an array based on the result of the user-supplied closure and returns a new sorted array, leaving the original array unchanged.
1\. let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
Copy the code
General function sort
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
Copy the code
Closure expression sort
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
Copy the code
The contents of closures are inside {}, with the parameter and return value types declared before the in keyword, followed by the closure body. However, the implementation of normal functions and closure expressions is basically the same, without any optimization of closures.
Type inference optimization
Swift can infer the closure’s parameter type and return value type from the sorted(by:) method, all of which can be ignored (inline closures of functions or methods always infer the parameter type and return value type) :
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
Copy the code
Return inference optimization
The need for a return type can also be inferred from sorted(by:), so:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
Copy the code
Parameter name shorthand optimization
Swift automatically provides shorthand parameters for the inline closure, $0, $1, $2, etc., s1, s2 in elements can be ignored.
reversedNames = names.sorted(by: { $0 > $1 } )
Copy the code
Operator optimization
The ‘>’ operator of String is a binary operator that returns a Boolean value. The statement 0>0 >0 >1 is automatically generated:
reversedNames = names.sorted(by: >)
Two trailing closures
If you need to pass a long closure to a function as the last argument, you can use trailing closures to keep the call formatted nicely.
func someFunctionThatTakesAClosure(closure: // function body goes here}// here's how you call this function without using a trailing closure:someFunctionThatTakesAClosure(closure: {// General closure as argument // Closure's body goes here})// Here's how you call this function with a trailing closure Home: someFunctionThatTakesAClosure () {/ / following closure as a parameter / / trailing closure 's body goes here}Copy the code
Sorted (by: >)
reversedNames = names.sorted(by: ){ $0 > $1 }
Copy the code
Value capture
Closures can capture constants and variables in the context and modify them in the closure, even if the definition context does not exist.
Refer to capture
In Swift, the simplest closure that can capture a constant or variable is a nested function.
The following function makeIncrementer contains a nested function called incrementer that captures two values, runningTotal and amount, The makeIncrementer ** function returns nested functions ** incrementer.
func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer }Copy the code
Copy to capture
As an optimization, Swift may capture a copy of the variable if the closure cannot modify the variable after creation and the variable itself is immutable. Swift handles all memory management when variables are no longer needed.
Strong reference ring
If a closure is assigned to a property of a class instance and the closure captures instance variables, a strong reference ring is formed between the closure and the instance variables. Swift uses capture lists to break Strong Reference loops, see Strong Reference Cycles for Closures for more information.
Closures are reference types
let incrementByTen = makeIncrementer(forIncrement: 10)let incrementByTen = makeIncrementer(forIncrement: 7)
Copy the code
In the above example, incrementBySeven and incrementByTen are constants, but closure constants can still operate on the captured variable runningTotal. This is because closures and functions are reference types.
When you assign a function or closure to a constant or variable, you are essentially setting the constant or variable as a reference to the function or closure, namely incrementByTen and incrementByTen reference not to the closure itself.
Three escape closure
Function escaping closures are closures that are called after a function returns, passed to the function as arguments that declare the closure argument to be the @escaping closure keyword.
Global variable escape method
One implementation of escaping a closure is to store the closure in a variable defined outside the function. In the following example, a compilation error is generated if the @escaping attribute is not used:
Var completionHandlers = [() - > Void] () / / store the escape of closure func someFunctionWithEscapingClosure (completionHandler: @ escaping () - > Void) {completionHandlers. Append (completionHandler) / / to join the completionHandler completionHandlers won't call // Do not use the @escaping attribute, a compilation error occurs}Copy the code
Attribute escape method
Closure escape can be achieved by storing closures in properties.
Nested escape method
Closure escape can be achieved by storing closures in escape closures.
Strong reference ring
If self is referenced in an escape closure and self refers to an instance of a class, you need to be careful to avoid strong reference rings. Capturing self in an escape closure is very easy to create strong Reference loops, see Automatic Reference Counting.
Closure --> self --> someInstance (Maybe the closure which creates a cycle)Copy the code
In general, closures implicitly capture variables. If you want to capture self, you can either explicitly spell self or write self to the closure capture list. In escape closures, spelling the self need to display (someFunctionWithEscapingClosure (:)), in the escape closure can implicitly using the self (someFunctionWithNonescapingClosure (:)).
Use self as shown in the escape closure
func someFunctionWithNonescapingClosure(closure: () -> Void) { closure() } class SomeClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { self.x = 100} / / escape closures shows the use of the self someFunctionWithNonescapingClosure {x = 200}}} let the instance = SomeClass () instance.doSomething() print(instance.x) // Prints "200" completionHandlers.first? () print(instance.x) // Prints "100"Copy the code
Implicitly use self by capturing self in the closure’s capture list
class SomeOtherClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { [self] in x = 100 } / / by the capture of the closure list capture the self someFunctionWithNonescapingClosure {x = 200}}}Copy the code
Structs or enumeration instances do not cause strong reference rings
Structs and enumerations are value types, and the reference chain in which value types participate does not produce a strong reference ring. When self is an instance of a structure or enumeration, the escape closure cannot capture a mutable reference to self. Structures, or Enumerations, do not allow shared variables. See Structures and Enumerations Are Value Types.
struct SomeStruct {
var x = 10
mutating func doSomething() {
someFunctionWithNonescapingClosure { x = 200 } // Ok
someFunctionWithEscapingClosure { x = 100 } // Error
}
}
Copy the code
Four Automatic closures (Expression closures)
An automatic closure is an automatically created expression closure that can be used as a function argument. When called with no arguments, it returns the value of the expression inside the closure, as in:
let customerProvider = { customersInLine.remove(at: 0) }
Copy the code
For example, the
assert(condition:message:file:line:)
function takes an autoclosure for itscondition
andmessage
parameters; itscondition
parameter is evaluated only in debug builds and itsmessage
parameter is evaluated only ifcondition
isfalse
.
Expression delay evaluation
Automatic closures pass a block of expression code to execute, not the result of its execution. The following code shows how to implement delay evaluation using automatic closures:
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] print(customersInLine.count) // Prints "5" let customerProvider = { customersInLine.remove(at: 0)} // Display the closing print(customerSinline.count) // Delay executing customerProvider // Prints "5" print("Now serving \(customerProvider())! " ) // Execute customerProvider, remove "Chris" // Prints "Now serving Chris!" print(customersInLine.count) // Prints "4"Copy the code
Note that the type of
customerProvider
is notString
But () -> String — a function with no parameters that returns a String.
{} displays closures
Display closures as function arguments to delay execution:
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"] func serve(customer customerProvider: () -> String) { print("Now serving \(customerProvider())!" )} serve(customer: {customersinline.remove (at: 0)}) // show closing // Prints "Now serving Alex!"Copy the code
Automatic closure
Automatic closures as function arguments to implement delayed execution:
// customersInLine is ["Ewa", "Barry", "Daniella"] func serve(customer customerProvider: @autoclosure () -> String) { print("Now serving \(customerProvider())!" )} serve(customer: customersinline.remove (at: 0)) // Prints "Now serving Ewa!"Copy the code
Overusing autoclosures can make your code hard to understand. The context and function name should make it clear that evaluation is being deferred.
Automatic closure escape
Automatic closure escape is implemented using both the @Autoclosure and @escaping attributes.
let customersInLine = ["Barry", "Daniella"] var customerProviders: [() -> String] = [] // Closure type func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) { customerProviders.append(customerProvider) } collectCustomerProviders(customersInLine.remove(at: 0)) collectCustomerProviders(customersInLine.remove(at: 0)) print("Collected \(customerProviders.count) closures.") // Prints "Collected 2 closures." for customerProvider in customerProviders { print("Now serving \(customerProvider())!" ) } // Prints "Now serving Barry!" // Prints "Now serving Daniella!"Copy the code
The above code uses the customerProviders array to store closure references of type () -> String.
5. Reference Capture
Implicit capture
Implicit capture is very convenient, and is the source of many spooky bugs and internal problems, such as presenters moving out of the view before the following closure is executed.
func presentDelayedConfirmation(in presenter: UIViewController) { let queue = DispatchQueue.main queue.asyncAfter(deadline: .now() + 3) { let alert = UIAlertController( title: "..." , message: "..." , preferredStyle: .alert) // By referring to 'presenter', // the closure will automatically capture the instance of 'Presenter' and hold it until the closure is released. Presenter (alert, animated: true)}}Copy the code
According to capture
You can use capture lists to explicitly customize how a closure captures the object or value it references. Below we use the capture list to specify weak capture for the variable Presenter [weak Presenter] (the default is strong capture).
func presentDelayedConfirmation(in presenter: UIViewController) { let queue = DispatchQueue.main queue.asyncAfter(deadline: .now() + 3) {[weak presenter] in // Guard let presenter = Presenter else {return} let alert = UIAlertController( title: "..." , message: "..." , preferredStyle: .alert ) presenter.present(alert, animated: true) } }Copy the code
No references are held (unowned References)
Unheld references are very similar to weak references, and the result is equivalent to force-unwrapped optionals. Do not hold the reference setting the reference is non-optional and will crash if it references the freed object.
class UserModelController {
...
init(user: User, storage: UserStorage) {
...
storage.addObserver(forID: user.id) { [unowned self] user in
self.user = user
}
}
}
Copy the code
Note: Each optional type takes up an additional 1 byte of memory space, 8 bytes of memory space for 8 bytes of pairs, and 1 byte of memory space for multiple optional types is not conflable. Using the optional type takes up nearly double the space.
Six data
The official documentation
Docs.swift.org/swift-book/… closure
Docs.swift.org/swift-book/… Automatic reference counting
The three parties
www.swiftbysundell.com/articles/sw… Closure capture mechanism
Cloud.tencent.com/developer/a… Swift object memory model exploration (I) to be further studied !!!!
Programmer. Ink/think/resea… Swift object memory model
medium.com/@venki0119/… Memory leak of closure
medium.com/@JimmyMAnde… Value types on the stack reference types on the heap
Academy. Realm. IO/posts/goto -… Memory layout
Andela.com/insights/th… lldb for swift