Closure expressions and their optimization abbreviations

Closure is a closed block of functionality that can be passed as a parameter or called directly in code. Closure in Swift is similar in function to block in C/OC or lambdas in other languages.

Closure expression syntax

{ (parameters) -> return type in
    statements
}
Copy the code

Parameters) -> Return type is the closure’s type, including the definition of input and output parameters. Statements describe the functions provided by the closure. Given Array’s sorted(by:) example, see how closure is used as a function argument

let names = ["Chris"."Alex"."Ewa"."Barry"."Daniella"]
reversedNames = names.sorted(by: { (s1: String, s2: String) - >Bool in
    return s1 > s2
})
Copy the code

A Closure shorthand

  1. Derive parameter types from context
  2. An indirect return can omit the reture keyword if there is only one return expression in a closure
  3. To abbreviate the input parameter name, use$0, $1, $2And so on instead of the input parameter name
  4. You can use operator methods directly
  5. Trailing closure: If the closure is the last argument to the function, you can write the closure after the function’s parentheses. The closure is still the function’s argument. If closure is the only argument to a function, the function’s parentheses can also be omitted.
reversedNames = names.sorted(by: { (s1: String, s2: String) - >Bool in
    return s1 > s2
})

// Inferring Type From Context
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })

// Implicit Returns
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 })

// Shorthand Argument Names
reversedNames = names.sorted(by: { $0 > The $1 })

// Operator Methods
reversedNames = names.sorted(by: >)

// Trailing Closures
reversedNames = names.sorted { $0 > The $1 }
Copy the code

Escaping Closures

When a closure is passed as an argument to a function, it is said to have escaped a function if the closure is called after the function return. Closure can be preceded by the @escaping keyword to indicate that closure is escaping.

Example

In the following example completionHandler is escaping a closure, because of the closure before someFunctionWithEscapingClosure function return has not been called.

var completionHandlers =[() - >Void] ()func someFunctionWithEscapingClosure(completionHandler: @escaping() - >Void) {
    completionHandlers.append(completionHandler)
}
Copy the code

An escape closure explicitly captures variables

Non-escape closures implicitly capture self, while escape closures show capture self. Escaping Closure shows that capturing self makes it easier for developers to find circular reference problems. Instead of escaping the closure lifecycle, only the FA is in the scope of the function, and the closure does not exist after the function returns, so there is no problem with circular references. An escape closure is similar to an on-heap block of OC, and a non-escape closure is similar to an on-stack block.

func someFunctionWithNonescapingClosure(closure: () - >Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething(a) {
        someFunctionWithEscapingClosure { self.x = 100 }
        / / another writing someFunctionWithEscapingClosure {[to self] in x = 100}
        someFunctionWithNonescapingClosure { x = 200}}}Copy the code

Autoclosures automatic closures

When an expression is passed as an argument to a function, automatic closures are automatically created to encapsulate the expression. Instead of passing the closure argument to the function, you pass an expression directly. This approach improves the readability and expressiveness of the code. @autoclosure marks an automatic closure.

func logIfTrue(predicate: () - >Bool) {
    if predicate() {
        print("True")}}func logIfTrue(predicate: @autoclosure() - >Bool) {
    if predicate() {
        print("True")}}Copy the code

According to the function definition above, there are two prototypes of the function with the same name, one of which takes Closure and the other Bool

logIfTrue { 2 > 1 }    / / ✔ ️ print "True"
logIfTrue(predicate: 2 > 1)      // ✔️ print "True" expression 2>1 is automatically wrapped as closure
Copy the code

With automatic closures, you can omit the braces and use the closure return type as the input type, making the function call more natural. But this also means that the Autoclosure does not take any arguments, otherwise the function arguments would have to be of type Closure. Overuse of @Autoclosure can make code difficult to understand because the closure autogeneration is hidden.

For a discussion of How autoclosure can delay parameter calculation, see this article How to Use @Autoclosure in Swift to improve Performance

Strong Cycle Reference Indicates the Cycle Reference

What circular references are and how they are generated are not discussed here. You can refer to Automatic Reference Counting directly. Swift provides two ways to solve the circular reference problem

  • weak references
  • unowned references

Weak References (owned)

Weak References does not hold the instance it points to, so it does not affect ARC’s ability to free referenced instances. Weak Reference may still point to weak reference instances when they are freed, so ARC automatically sets weak Reference to nil when the object is freed. Since weak References can become nil at run time, they are always declared as optional.

Suppose instance A holds instance B, whose lifetime is shorter than A’s (B will be released before A). If A and B have A cyclic reference, A can point to B with weak reference to break the cyclic reference.

Unowned References (not owned)

The Unowned References section also does not hold the instance to which it points. The unowned reference always has a value, so ARC does not automatically set the unowned reference to nil. Use unowned only if you are sure that the instance reference points to will not be freed.

Suppose instance A holds instance B, whose lifetime is longer or the same as that of A (B will not be released until A is released). If A and B form A circular reference, A can use owned Reference to point to B to break the circular reference.

Resolve the Closure’s circular reference

Circular references can be broken by specifying a capture list for a closure that defines the rules for using the capture object within the closure. You typically use weak or unowned to decorate an object, depending on the relationship between the objects.

  • Define the captured object as unowned: when a closure and its captured objects always refer to each other and are released at the same time
  • Define the captured object as weak: The captured object will be released at some point in the future, at which point the reference will be set to nil, so weak Reference is always optional

Closure prototype with capture list

{[unowned self.weak delegate = self.delegate] 
    (parameters) -> return type in
        statements
}

Copy the code

reference

  1. Swift Document – Closures
  2. @ AUTOCLOSURE and????
  3. Weak self and unowned self explained in Swift
  4. Swift Document – Automatic Reference Counting
  5. How to use @autoclosure in Swift to improve performance