closure
Closures in Swift are similar to blocks in Objective-C, and similar to lambdas in other programming languages, in that closures can capture and store references to any constants and variables from the context in which they are defined.
Closures come in three forms:
- Global functions are named closures that do not capture any values.
- Nested functions are closures with names that capture values from their enclosing functions.
- Closure expressions are unnamed closures written in lightweight syntax that capture values from the surrounding context.
Closure expression syntax has the following general form:
{ (parameters) -> return type in
statements
}
Copy the code
Parameter, return value type, and closure body. Take the sorted(by:) array from Swift as an example:
Let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]Copy the code
The sort(by:) method takes a closure that takes two arguments of the same type as the contents of the array and returns a Bool that returns whether the first value should appear before or after the second value after the sort of values. The sort closure needs to return true if the first value appears before the second, otherwise false:
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
Copy the code
However, this is a rather lengthy way to write a function that is essentially a single expression (a > b). In this case, it is better to use closure expression syntax to write the inline sorted closure, which looks like this when changed to closure:
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
Copy the code
In contrast, the arguments and return value types of an inline closure expression are enclosed in curly braces. The beginning of a closure body is introduced by the in keyword. This keyword indicates that the definition of the parameters and return types of the closure is complete and that the body of the closure is about to begin. This format also conforms to the previous definition of the closure. As you can see from this example, the overall call to the sorted(by:) method remains the same. The entire argument to the method is still in parentheses. However, this parameter is now an inline closure instead of a function. In fact, when a function is defined, the parameter is still a function type, but when called, the parameter of the function type can be passed as a closure.
Closures in Swift have many features, listed below.
Infer types from context
Because the sort closure is passed to the method as a parameter, Swift can infer its parameter type and the type of its return value. The sorted(by:) method is called on an array of strings, so its argument must be a function of type (String, String) -> Bool. This means that the (String, String) and Bool types need not be written as part of the closure expression definition. Since all types can be inferred, the return arrow (->) and parentheses around parameter names can also be omitted:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
Copy the code
When the closure is passed to the method as a parameter, Swift can infer the parameter type and return value type. Therefore, you don’t need to write it all out.
Implicit return
An implicit return can omit the return keyword from the declaration:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
Copy the code
Because the body of the closure needs to return a Bool expression (s1 > s2), the closure must have a return value and the return keyword can be omitted.
Simplified parameter name
Swift automatic inline closure provide shorthand parameter name, you can use these names $0, $1, $2 to refer to the closure of parameter values, if use the abbreviations in the closure of parameter names, can omit the closure from the definition of parameters, and the parameters of the abbreviated name of the number and type will be inferred from the expected function type. The in keyword can also be omitted because the closure expression consists entirely of its body:
reversedNames = names.sorted(by: { $0 > $1 } )
Copy the code
Where $0 and $1 refer to the first and second string arguments of the closure.
Following the closure
If you need to pass a closure as the last argument to a function and the closure is long, you can write it as a trailing closure. Trailing closures are still used as arguments to the function, except that a closing closure is written after the parentheses of the function call, rather than inside parentheses as above. Tail-closure is most useful when the closure is long enough that it cannot be written inline on a line. A function call can have multiple trailing closures, but the argument label of the first closure is omitted during the function call.
Define a function where closure is a void function with no arguments: void closure;
func someFunctionThatTakesAClosure(closure: () - > Void) {/ / function body goes here} / / call: do not use the following closure someFunctionThatTakesAClosure (closure: {/ / closure 's body goes here}) / / using the following closure call: someFunctionThatTakesAClosure () {/ / trailing closure' s body goes here}Copy the code
The closure for the string sort example above would then look like this:
reversedNames = names.sorted() { $0 > $1 }
Copy the code
If a closure expression is the only argument to a function or method, the closing pair of parentheses () can be omitted:
reversedNames = names.sorted { $0 > $1 }
Copy the code
If a function accepts multiple closures, you can omit the argument tags of the first trailing closure and mark only the trailing closures that follow. For example, the following function loads an image for the gallery:
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
if let picture = download("photo.jpg", from: server) {
completion(picture)
} else {
onFailure()
}
}
Copy the code
You can omit the completion tag for the first closure when calling:
loadPicture(from: someServer) { picture in
someView.currentPicture = picture
} onFailure: {
print("Couldn't download the next picture.")
}
Copy the code
The value of the capture
OC blocks can capture the values of local variables, as in Swift, but they are more powerful than blocks. Closures can capture constants and variables from the context around which they are defined, and reference and modify the values of these constants and variables within their body, without the use of modifiers such as __block. Even if the original scope that defined these constants and variables no longer exists.
In Swift, the simplest form of closure that can capture values is a nested function written in another function body. A nested function can capture any parameters of its external function, as well as any constants and variables defined within the external function.
Here is an example of a function called makeIncrementer that contains a nested function called incrementer:
func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer }Copy the code
As you can see incrementer doesn’t have any parameters, but it captures references to amount and runningTotal, ensuring that they don’t disappear at the end of the makeIncrementer call, Also ensure that runningTotal is available the next time the Incrementer function is called. Inside Swift, which essentially stores a copy of their values, handles all memory management of the variables involved when they are no longer needed.
Here is an example of calling makeIncrementer and printing the result:
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30
Copy the code
Escape a closure
A Swift closure is passed as an argument to a function, and is called after the function returns. The documentation is somewhat obscure, and I understand escape because it escapes the scope of this function. In OC, similar to the Block callback to a network request, escaping closure needs to be preceded by @escaping to declare that the closure permits escape.
var completionHandlers = [() -> Void]()
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
Copy the code
This is an example of documentation where @escaping declares that completionHandler is a escaping closure, because this closure is not called before the end of the function, but is stored with completionHandlers, so the @escaping keyword must be added otherwise the compiler will report an error.
In the pre-literal capture example, the regular closure capture variable does not need to declare self explicitly, but the escape closure must declare self explicitly, as shown below:
func someFunctionWithNonescapingClosure(closure: () -> Void) { closure() } class SomeClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { self.x = 100 } someFunctionWithNonescapingClosure { x = 200 } } } let instance = SomeClass() instance.doSomething() print(instance.x) // Prints "200" completionHandlers.first? () print(instance.x) // Prints "100"Copy the code
The purpose of this is to remind us to make sure that there are no circular references between self and the closure.
Automatic closure
An automatic closure is a shorthand for a closure that is automatically created and passed to a function as an argument, as described in the documentation. By adding the keyword @autoclosure to the argument type, the function can be called without the curly braces around the closure, just like a normal expression. The differences between automatic closures and normal closures are as follows:
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"] func serve(customer customerProvider: () -> String) { print("Now serving \(customerProvider())!" ) } serve(customer: { customersInLine.remove(at: 0) } ) // Prints "Now serving Alex!" // 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
The automatic closure looks more streamlined, with the Customer parameter passing more like a string rather than passing a closure wrapped in curly braces. Be careful not to overuse automatic closures as they can make your code difficult to understand. If you do use them, it should be clear in the context and function name that this is an automatic closure, and that it executes later rather than immediately.
Automatic closures can be used in conjunction with escape closures, with @autoClosure @escaping preceded the argument type. In the appearance of coding, function calls can be done with closure braces {} removed, which is a feature of automatic closures, and the closure can be added to an array and executed outside the scope of a function. This is the property of the escape closure and is used as follows:
// customersInLine is ["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.") // Prints "Collected 2 closures." for customerProvider in customerProviders { print("Now serving \(customerProvider())!" ) } // Prints "Now serving Barry!" // Prints "Now serving Daniella!"Copy the code