共计 8390 个字符,预计需要花费 21 分钟才能阅读完成。
Closures
Closures are self-contained blocks of functionality that can be passed around and used in your code.
Global and nested functions are special cases of closures.
- Global functions are closures that have name and don’t capture any value.
- Nested functions are closures that have a name and can capture values from the enclosing function.
- Closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context.
The sort()
will sort at original array, and sorted()
will sort and return a new sorted array.
Usage
-
Pass a closure to a function as a tool to do something.
For example, pass as a comparator to a sorted function.
-
Pass multiple closure to a function as some handler to deal the different circumstances after the function call.
For example, pass a
completion
andonFailure
closure to aloadPicture
function, and to do reaction for the picture load complete or fail.
Closures’Syntax
{(parameters) ->
return type in
statements, the closure's body
}
Pass a function closure to a function, as a tool to do some work.
func compare(_ s1: String, _ s2: String) -> Bool{return s1 > s2}
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var sortedNames = names.sorted(by: compare) // Here we pass a comparator to the sorted method.
// Here the sortedNames is ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
An easy way to pass a function (or say a comparator) to a sorted method.
This is the inline closure.
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var sortedNames = names.sorted({(_ s1: String, _ s2: String) ->
Bool in
return s1 > s2
})
Inferring Type From Context
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var sortedNames = names.sorted(by: { s1, s2 in return s1 > s2} )
// The compiler will infer the parameter types and the return value type.
Implicit Return in Single-Expression Closures
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var sortedNames = names.sorted(by: { s1, s2 in s1 > s2} )
Shorthand Argument Names for Inline Closures
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var sortedNames = names.sorted(by: { $0 > $1}) // '$0' means that getting the first parameter to do the comparation..
Operator Methods
String
type has its string-specific implementation of the grater than operator‘>’can be a method that has two parameters of typeString
, and return a value of typeBool
.
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
var sortedNames = names.sorted(by: >)
Trailing Closures
Without Trailing Closures
func someFunction(closure: () -> void) {
}
someFunction(closure: {// closure's body, to do something.})
var sortedNames = names.sorted(by: {$0 >$1})
With Trailing Closures
someFunction() { // Trailing closure's body}
var sortedNames = names.sorted() {$0 > $1}
With Trailing Closures Omitting Parentheses
var sortedNames = names.sorted {$0 > $1}
Apply a Provided Closure to Each Element of A Map
Using the map() method, and give a closure to it. (The map method here omit the parentheses)
The closure say that input a Int
, output a String
, the process of the closure is after the in
.
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
] // A dictionary.
let numbers = [16, 58, 510]
// The type of number can be omitted, because it can be inferred from the value the map to be mapped.
let numbersStr = numbers.map {(number: Int) -> String in
var num = number // The 'number' parameter can't be modified, so we set a'num' to use its value.
var str: String = ""
repeat {str = digitNames[num % 10]! + str // remainder operate to get the num's last digit.
num /= 10
} while num > 0
return str
}
// Now the numbersStr is ["OneSix", "FiveEight","FiveOneZero"]
In the above, digitNames[num % 10]!
use the exclamation point, because the result from digitNames
is optional, it may be not found by num%10
.(The exclamation point is used to force-unwrap the String
value)
Function Use Multiple Closures
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {if let picture = download("photo.jpg", from: server) {completion(picture)
} else {onFailure()
}
}
When we call the loadPicture
function, we should give it a completion
and an onFailure
closure as two handlers, so that after the network service done the work, with the handler, we can do something when the download complete or fail.
Capturing Values
A closure can capture constants and variables from the surrounding context in which it’s define. It can read those constants and modify those variables, even if the original scope no longer exist.
-
Nested function is a form of closure that can capture values from it outside scope.
func run(count: Int) -> () -> Int{ var a = 5 func wait() -> Int { // The function 'wait' capture the reference of 'a' and 'count' from the surrounding function. a += count return a } func go() -> Int { a += count * 2 return a } return wait // It returns a function, not a simple value. } print(run(count: 4)()) // Print: 9 // Capturing by reference ensures that 'a' and 'count' don’t disappear when the call to 'run' ends, and also ensures that 'a' is available the next time the 'wait' function is called. // If the value isn't mutated by a closure, Swift may store a copy of this value instead of capture it. let fun = run(count: 10) print(fun()) print(fun())
-
Result
9
15
25The first time use the
run(count: 4)
, it has a scope for run.The second time use the
run(count: 10)
, it has another scope.
-
NOTE
If you assign a closure to a property of a class instance,
and the closure captures that instance by referring to the instance or its members,
you will create a strong reference cycle between the closure and the instance.
Swift uses capture lists to break these strong reference cycles.
Closures Are Reference Types
let fun = run(count: 10)
print(fun())
print(fun())
let myFun = fun
print(myFun())
-
Result
15
25
35
Escaping Closures
In order to use the closure parameter as a reference before defining the closure’s body, we should specify the closure parameter with @escaping
, otherwise it will cause the compile-time error.
The escaping indicate that the closure is store in a value that’s defined outside the function.
var handlers: [() -> Void] = []
func addHandle(handle: @escaping () -> Void) {handlers.append(handle)
let han: () -> Void
han = handle // The handle must be specified with @escaping
func run() {}
addHandle(handle: run)
The code follow is not allow, because the handle has’t been defined(create instance) before the addHandle return.
func addHandle(handle: () -> Void) -> () -> Void {return handle // The closure 'handle' can't be use}
Add the @escaping
to solve the problem above. It will escape(store in a value outside the function) and then call the reference.
func addHandle(handle: @escaping () -> Void) -> () -> Void {return handle}
Capturing self
Capturing self
in an escaping closure makes it easy to accidentally create a strong reference cycle.
When we want to use the properties in current instance of the class, must use self
explicitly, or include self
in the closure’s capture list.
Use self
Explicitly
class Person {
var age = 23
func printAge(){
self.age = 24
print(self.age)
}
func printAgeOther(age: Int){print(self.age)} // Here the self is needed certainly. Otherwise we can't derferentiate the'age' of the instance and current function.
}
var person = Person()
person.printAgeOther(age: 4)
Include the self
in Capture List
func doSomething(_ fun: () -> Void){fun()
}
class Person {
var age = 23
func printAgeOther(){[self] // The capture list.
age = 22
print(age) // Here is use the 'self.age'
}
func myFunc() {doSomething(){[self] in
print(age) // Here is use the 'self.age'
}
}
}
var person = Person()
person.printAgeOther()
person.myFunc()
-
Result
22
22Capture
self
instructure
orenumeration
- Can always refer to
self
implicitly. - But an escaping closure can’t capture a mutable reference to
self
.
func doSomething(_ fun: () -> Void){fun() } func doSomethingElse(_ fun: @escaping () -> Void){fun() } struct Person { var age = 23 mutating func myFunc(){doSomething(){ age = 18 print(age) } // The follow code, 'doSomethingElse' function's parameter is escaping, it will trigger compiler error. // doSomethingElse(){ // age = 17 // print(age) // } } } var person = Person() person.myFunc()
## Autoclosures
When the closure is assigned to a
var
or alet
, it hasn’t been call, just give its function reference to other.It can be use as delays evaluation or overtime judge.
var names = ["Mike", "Alice", "Jhon"] print(names.count) let theRemover = {names.remove(at: 0)} // Get the closure's reference. print(names.count) print("Remove element: \(theRemover())") // Call the closure, run the closure's body first time. print(names.count)
-
Result
3
3
Remove element: Mike
2theRemover
has a type() -> String
- Can always refer to
Also, we can write it in function like below.
```swift
var names = ["Mike", "Alice", "Jhon"]
print(names.count)
func doSomething(action: () -> String){print("Remove elsemet: \(action())")
}
doSomething(action: {names.remove(at: 0)})
print(names.count)
```
Use the @autoclosure
, it has the same result as the code above.
```swift
var names = ["Mike", "Alice", "Jhon"]
print(names.count)
func doSomething(action: @autoclosure () -> String){print("Remove elsemet: \(action())")
}
doSomething(action: names.remove(at: 0))
print(names.count)
```
Use the `@autoclosure` and `@escaping` .
```swift
var names = ["Mike", "Alice", "Jhon"]
print(names.count)
var funs: [() -> String] = []
func doSomething(action: @autoclosure @escaping () -> String){print("Remove elsemet: \(action())")
funs.append(action) // It need the condition that action is escaping.(maybe because the action is a reference?))
name.append("Jane") // The "Jane" can be added to name.
}
doSomething(action: names.remove(at: 0))
print(names.count)
```
The escaping means that the argument action
is allowed to escape the function’s scope.