define

Closures are self-contained blocks of function code that can be passed and used in code. Closures in Swift are similar to code blocks in C and Objective-C, and to anonymous functions in some other programming languages.

Closures can capture and store references to arbitrary constants and variables in their context. It’s called wrapping constants and variables. Swift manages all memory operations involved in the capture process for you.

Special form

Global and nested functions are actually special closures that take one of three forms:

  • Global functionIt’s a guy with a name butNo values are capturedThe closure of
  • Nested functionIs one that has a name and can capture itValues in the domain of a closed functionThe closure of
  • Closure expressionIs a way to exploitLightweight syntaxWritten to capture the value of a variable or constant in its contextanonymousclosure

Optimizing the expression

Swift’s closure expressions have a concise style and encourage syntactic optimizations in common scenarios. The main optimizations are as follows:

  • Use context inferenceparameterandThe return valuetype
  • Implicitly returns a single-expression closure, that is, the single-expression closure can be omittedreturnThe keyword
  • Parameter Name Abbreviation
  • Trailing closure syntax

Closure expression

When nested functions are part of a complex function, their self-contained code block definitions and naming forms make it easier to use. Of course, it can be useful to write code with function-like structures that are not fully declared and have no function names, especially if the code involves methods that take functions as arguments. Closure expressions are a way to build inline closures, and their syntax is concise. Closure expressions provide several optimized syntactic abbreviations without losing the clarity of their syntax. The following shows this process by iterating on the sorted(by:) case, each iteration describing the same functionality in a more concise way.

The Swift library provides a method called sorted(by:), which sorts the values (type determinations) in an array based on the sort closure expression you provide. Once it’s done sorting, the sorted(by:) method returns a new array of the same size as the old array type, with the elements sorted in the correct order. The original array is not modified by the sorted(by:) method. The following closure expression example uses the sorted(by:) method to alphabetically reverse an array of String. Here is the initial array:

let names =["Chris","Alex","Ewa","Barry","Daniella"]Copy the code

The sorted(by:) method takes a closure that passes in two values of the same type as the array elements and returns a Boolean value indicating whether the first argument passed in when the sort is finished comes before or after the second. The sort closure needs to return true if the first argument value comes before the second, and false otherwise. This example sorts an array of type String, so the sort closure must be (String, String) -> Bool. One way to provide a sorted closure function is to write an ordinary function that meets its type requirements and pass it in as an argument to the sorted(by:) method:

func backward(_ s1: String._ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames 为 [“Ewa”, “Daniella”, “Chris”, “Barry”, “Alex”]
Copy the code

If the first string s1 is greater than the second string s2, the backward(_:_:) function returns true, indicating that S1 should come before S2 in the new array. For characters in a string, greater than means “later in alphabetical order.” This means that the letter B is greater than the letter A, and the string Tom is greater than the string Tim. The closure will be alphabetically sorted in reverse order, and Barry will be ranked before Alex. However, it is too tedious to write what is actually a simple expression (a > b) in this way. For this example, it is better to construct an inline sorted closure using closure expression syntax.

Following the closure

If you need to pass a long closure expression to a function as the last argument, it is useful to replace the closure with a trailing closure. A trailing closure is a closure expression written after the function’s parentheses, which the function supports calling as the last argument. When using a trailing closure, you don’t have to write its argument tag:

func someFunctionThatTakesAClosure(closure: () - >Void) {
    // The body of the function
}
Copy the code
// The following function calls are made without trailing closures
someFunctionThatTakesAClosure(closure: {
    // The body of the closure
})
Copy the code
// Following are function calls using trailing closures
someFunctionThatTakesAClosure() {
    // The body of the closure
}
Copy the code

In closure expression syntax, the string sort closure in the section can be overwritten as a trailing package outside the parentheses of the sorted(by:) method:

reversedNames = names.sorted() { $0 > The $1 }
Copy the code

If the closure expression is the only argument to a function or method, then you can even omit () when using trailing closures:

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

Trailing closures become useful when closures are too long to write on a single line. For example, Swift’s Array type has a map(_:) method that takes a closure expression as its only argument. This closure function is called once for each element in the array and returns the value mapped to that element. The exact mapping and return value type are specified by the closure. When the closure provided to the array is applied to each array element, the map(_:) method returns a new array containing the values mapped to the elements in the original array. The following example shows how to convert an Int array [16, 58, 510] into an array [” OneSix “, “FiveEight”, “FiveOneZero”] using a trailing closure in the map(_:) method:

let digitNames = [
    0:"Zero",1:"One",2:"Two",3:"Three",4:"Four",5:"Five",6:"Six",7:"Seven",8:"Eight",9:"Nine"]let numbers = [16.58.510]
Copy the code

The code above creates a dictionary that maps integer digits to their English version names. It also defines an array of integers to be converted to an array of strings.

You can now create the corresponding string version of the array by passing a trailing closure to the map(_:) method of the numbers array:

let strings = numbers.map {
    (number) -> String in
    var number = number
    var output =""repeat {
        output = digitNames[number % 10]! + output
        number / = 10
    } while number > 0
    return output
}
// Strings constants are inferred to be strings array, i.e. [String]
// The value is [" OneSix ", "FiveEight", "FiveOneZero"]
Copy the code

Map (_:) calls a closure expression once for each element in the array. You do not need to specify the type of the closure’s input parameter number, because you can infer from the array type to be mapped. In this example, the value of the local variable number is taken from the number parameter in the closure, so it can be modified inside the closure function (arguments to closures or functions are always constant). The closure expression specifies a String return type to indicate that the new array storing the mapped values is of type String. The closure expression creates a string called output and returns it each time it is called. It calculates the last digit using the complement operator Number % 10 and retrieves the mapped string using the digitNames dictionary. This closure can be used to create a string representation of any positive integer. Note the dictionary digitNames followed by an exclamation mark! Because the dictionary subscript returns an optional value, the search fails if the key does not exist. In the above example, because you can be certain that number % 10 is always a valid subscript for the digitNames dictionary, an exclamation point can be used to force-unwrap a value of type String in the return value of the optional type of the subscript. Strings taken from the digitNames dictionary are appended to the front of the output, reverse-order a string version of the number. (in the expression number % 10, if number is 16, returns 6,58 returns 8, and 510 returns 0.) The Number variable is divided by 10. Because it is an integer, the undivided part is ignored in the calculation. So 16 becomes 1,58 becomes 5,510 becomes 51. The process repeats until number /= 10 is 0, at which point the closure returns the string output and the map(_:) method adds the string to the map array. In the example above, the trailing closure syntax elegantly encapsulates the specific functionality of the closure after the function, eliminating the need to wrap the entire closure inside the parentheses of the map(_:) method.

Value capture

Closures can capture constants or variables in the context in which they are defined. Even if the original scope that defined these constants and variables no longer exists, closures can still reference and modify these values inside the closure function. In Swift, the simplest form of a closure that can capture values is a nested function, that is, a function defined within the body of another function. A nested function can capture all the parameters of its external function as well as the constants and variables defined. For example, there is a function called makeIncrementer that contains a nested function called incrementer. The nested function incrementer() captures two values, runningTotal and amount, from the context. After capturing these values, makeIncrementer returns Incrementer as a closure. Each time incrementer is called, it increments the value of runningTotal with amount.

func makeIncrementer(forIncrement amount: Int)- > () - >Int {
    var runningTotal = 0
    func incrementer(a) -> Int {
        runningTotal + = amount
        return runningTotal
    }
    return incrementer
}
Copy the code

MakeIncrementer returns type () -> Int. This means that it returns a function rather than a value of a simple type. This function takes no arguments on each call and only returns a value of type Int. The makeIncrementer(forIncrement:) function defines runningTotal, an integer variable with an initial value of 0, to store the current total. This value is the return value of incrementer. MakeIncrementer (forIncrement:) takes an Int with an external parameter named forIncrement and an internal parameter named amount. This parameter indicates the amount that runningTotal will increase each time incrementer is called. The makeIncrementer function also defines a nested function incrementer that performs the actual increment operation. This function simply increases the amount of runningTotal and returns it. If we consider the nested function incrementer() separately, it is a bit unusual:

func incrementer(a) -> Int {
    runningTotal + = amount
    return runningTotal
}
Copy the code

The incrementer() function does not take any arguments, but accesses the runningTotal and amount variables inside the function. This is because it captures references to the runningTotal and amount variables from the peripheral function. Capturing references ensures that the runningTotal and amount variables do not disappear after makeIncrementer is called, and that runningTotal is still present the next time incrementer is executed. Note that for optimization purposes, if a value is not changed by a closure or does not change after the closure is created, Swift may instead capture and save a copy of the value. Swift also takes care of all memory management for captured variables, including freeing variables that are no longer needed. Here is an example of using makeIncrementer:

let incrementByTen = makeIncrementer(forIncrement: 10)
Copy the code

This example defines a constant called incrementByTen that points to a Incrementer function that increments its runningTotal variable by 10 with each call. Calling this function multiple times yields the following result:

incrementByTen()
// The value returned is 10
incrementByTen()
// Returns a value of 20
incrementByTen()
// Returns a value of 30
Copy the code

If you create another incrementer, it will have its own reference to a new, independent runningTotal variable:

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// Returns a value of 7Calling the original incrementByTen again increments its own runningTotal variable, which has no connection to the variable captured in incrementBySeven: incrementByTen()// The value returned is 40
Copy the code

If you assign a closure to the properties of a class instance and the closure captures the instance by accessing the instance or its members, you will create a circular strong reference between the closure and the instance. Swift uses capture lists to break this circular strong reference.

Closures are reference types

In the above example, incrementBySeven and incrementByTen are constants, but the closure to which these constants point can still increase the value of the variable it captures. This is because functions and closures are reference types. Whether you assign a function or closure to a constant or variable, you are actually setting the constant or variable’s value as a reference to the corresponding function or closure. In the example above, the reference to the closure incrementByTen is a constant, not the closure content itself. This also means that if you assign a closure to two different constants or variables, both values will refer to the same closure:

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// The value returned is 50
Copy the code

Escape a closure

When a closure is passed to a function as an argument, but the closure is not executed until the function returns, the closure is said to escape from the function. When you define a function that takes a closure as an argument, you can tag @escaping before the argument name to indicate that the closure is allowed to escape from the function. One way to make a closure escape a function is to store the closure in a variable defined outside the function. For example, many functions that start asynchronous operations accept a closure parameter as a Completion handler. Such functions return immediately after the asynchronous operation begins, but the closure is not called until the asynchronous operation ends. In this case, the closure needs to escape from the function because the closure needs to be called after the function returns. Such as:

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

SomeFunctionWithEscapingClosure (_) function takes a closure as a parameter, the closure is added to the outside of a function defined in the array. If you do not tag this parameter with @escaping, you get a compile error. Labeling a closure as @escaping means that you must explicitly reference self in the closure. For example, in the code below, to the someFunctionWithEscapingClosure (_) in the closure is a escape closures, which means it need to explicitly cited the self. Relative to the someFunctionWithNonescapingClosure (_) in the closure is a non escape closures, which means it can implicit self reference.

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

class SomeClass {
    var x = 10
    func doSomething(a) {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200}}}let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Print "200"

completionHandlers.first?(a)print(instance.x)
// Print "100"
Copy the code

Automatic closure

An automatic closure is an automatically created closure that wraps expressions passed to functions as arguments. This closure takes no arguments and, when called, returns the value of the expression wrapped in it. This convenience syntax allows you to omit the curly braces of a closure and replace an explicit closure with a plain expression. We often call functions that use automatic closures, but rarely implement them. For example, assert (condition: message: file: line:) function accept automatic closure as its condition and the message parameters; Its condition parameter is evaluated only in debug mode, and its message parameter is evaluated only if condition is false. Automatic closures allow you to delay evaluation because the code snippet will not be executed until you call the closure. Delayed evaluation is beneficial for code that has Side effects and high computational costs because it allows you to control when the code executes. The following code shows how closures can be delayed to evaluate.

var customersInLine =["Chris","Alex","Ewa","Barry","Daniella"]print(customersInLine.count)
// Print "5"

let customerProvider = { customersInLine.remove(at: 0)}print(customersInLine.count)
// Print "5"

print("Now serving \(customerProvider())!").// Prints "Now serving Chris!
print(customersInLine.count)
// Print "4"
Copy the code

Although the first element of customersInLine is removed in the closure code, it is not removed until the closure is called. If the closure is never called, the expression inside the closure will never be executed, meaning that the elements in the list will never be removed. Note that the customerProvider type is not String, but () -> String, a function that takes no arguments and returns a String value. You get the same delayed evaluation behavior when you pass a closure as an argument to a function.

// customersInLine is [" Alex ", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () - >String) {
    print("Now serving \(customerProvider())!")} serve(customer: {customersinline.remove (at:0)})// Print "Now Serving Alex!"
Copy the code

The serve(customer:) function above takes an explicit closure that returns the customer’s name. The following version of serve(customer:) does the same thing, but instead of accepting an explicit closure, it accepts an automatic closure by marking the argument @autoclosure. Now you can call this function as if it takes a String argument instead of a closure. The customerProvider parameter is automatically converted to a closure because it is marked with the @AutoClosure feature.

// customersInLine is [" Ewa ", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure() - >String) {
    print("Now serving \(customerProvider())!")} serve (customer: customersInLine. Remove (at:0))
// Print "Now serving Ewa!"
Copy the code

Overusing Autoclosures can make your code unreadable. The context and function name should make it clear that the evaluation is deferred. If you want an automatic closure to “escape,” you should use both the @Autoclosure and @escaping attributes. For a description of the @escaping attribute, see the escape closure above.

// customersInLine I= [" Barry ", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping() - >String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected\ [customerProviders. Count) closures. ")// Print "Collected 2 closures."
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")}// Print "Now serving Barry!"
// Print "Now serving Daniella!"
Copy the code

In the above code, the collectCustomerProviders(_:) function does not call the passed customerProvider closure, but appends the closure to the customerProviders array. The array definition is outside the scope of the function, which means that the closure inside the array can be called after the function returns. Therefore, the customerProvider parameter must be allowed to escape out of function scope.

Swift learning group

Welcome to join my Swift learning wechat group for mutual supervision. My wechat account is Reese90