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:

  1. Global functions: closures that have names and do not capture any values;
  2. Nested functions: closures with names that can capture any value;
  3. 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
Parameters, can be input and output types, no default values, can be variable parameters, tuples;

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 its condition and message parameters; its condition parameter is evaluated only in debug builds and its message parameter is evaluated only if condition is false.

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 not String 

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