1.什么是闭包(Closure)?
闭包是一个完整的设计功能模块,可以在代码中传递和使用,类似于Object-C的block(但是还是有区别,下面会说明)或者其他语言的匿名函数(lambdas)。闭包可以捕获或者储存它们所在上下文的常量和变量。在Swift里等价于函数,是一等公民。
闭包有三种形式:
- 全局函数,有名字的闭包并且不捕获任何值(定义的一般函数)
- 嵌套函数,有名字的闭包,可以在闭包所在函数内部捕获值(函数里嵌套函数)
- 闭包表达式,没有名字的闭包,使用简洁的语法,可以在包裹闭包的上下文捕获值(闭包)
举例说明:
1 //Global function 2 func block() { 3 print("block") //block 4 } 5 6 //Nested function 7 func block(){ 8 let name = "block" 9 func printStr() { 10 print(name) 11 } 12 printStr() 13 } 14 block() //block 15 16 //Closure expression 17 let block = { 18 print("block") 19 } 20 block() //block
swift对闭包的表达式做了相关的优化:
- 从上下文推断传入参数和返回值
- 单一表表达式闭包的隐式返回
- 简短的参数名
- 尾随闭包
举例说明:
let numbers = [1, 3, 2, 4, 7, 8, 5] let sortedNums = numbers.sorted { (a: Int, b: Int) -> Bool in return a > b } //下面常量返回的都是[8, 7, 5, 4, 3, 2, 1] //从上下文推断传入参数和返回值 let sortedNums2 = numbers.sorted { (a, b) in return a > b } //单一表表达式闭包的隐式返回 let sortedNums = numbers.sorted { $0 > $1 } //简短的参数名 let sortedNums2 = numbers.sorted { (a, b) in return a > b } //尾随闭包 let sortedNums = numbers.sorted { $0 > $1 }
2.闭包的表达式语法(Closure Expressions)
定义:
{(parameters) -> return type in statements }
举一些例子:
//没有参数和返回值的block定义 let noParameterAndNoReturnValue: () -> () = { print("Hello world!") } //没有参数,有返回值的block定义 let noParameterAndReturnValue: () -> (Int) = { return 5 } //有一个参数和返回值的block定义 let oneParameterAndNoReturnValue: (Int) -> (Int) = { x in return x + 2 } //有多个参数和返回值的block定义 let mutipleParameterAndNoReturnValue: (Int, Int) -> (Int) = { (x, y) in return x + y }
3.简短的参数名字(Shorthand argument syntax)
1 // swift支持类型推断,什么意思呢?就是闭包的参数和返回类型都可以交给编译器去推断,在编码阶段就可以省略。闭包里面$0,$1代表的是传入的第一个参数和第二个参数,下面看代码: 2 3 //$0代表第一个参数,$1代表第二个参数,语法非常简洁 4 let numbers = [1, 3, 2, 4, 7, 8, 5] 5 let sortedNums = numbers.sorted { $0 > $1 } //[8, 7, 5, 4, 3, 2, 1] 6 // 顺便提一下函数(闭包)参数省略的过程,还是以数组降序为例: 7 8 let numbers = [1, 3, 2, 4, 7, 8, 5] 9 let sortedNums = numbers.sorted { (a: Int, b: Int) -> Bool in 10 return a > b 11 } //[8, 7, 5, 4, 3, 2, 1] 12 // 如果一个函数的返回类型和参数类型可以推导出来,则返回类型和参数类型都可以省略。删除:Int,-> Bool,上面的表达式变成: 13 14 let numbers = [1, 3, 2, 4, 7, 8, 5] 15 let sortedNums = numbers.sorted { (a, b) in 16 return a > b 17 } //[8, 7, 5, 4, 3, 2, 1] 18 // 如果参数的个数可以推导出来,则参数可以省略,使用$0,$1的方式代表参数。参数省略了,in关键字在这里就没有意义了,也可以省略,则上面的表达式变成: 19 20 let numbers = [1, 3, 2, 4, 7, 8, 5] 21 let sortedNums = numbers.sorted { 22 return $0 > $1 23 } //[8, 7, 5, 4, 3, 2, 1] 24 // 在swift里,如果函数体只有一行,则可以把return关键字省略,单一表达式闭包隐式返回,则代码进一步演变成: 25 26 let numbers = [1, 3, 2, 4, 7, 8, 5] 27 let sortedNums = numbers.sorted { 28 $0 > $1 29 } //[8, 7, 5, 4, 3, 2, 1] 30 // 最后,还能更近一步简化。可能很多人都郁闷了,就剩两个参数和操作符了,还能怎么简化?别急,swift里面还有一个简化规则,因为<也是函数,并且和函数sorted函数接收的参数个数,类型和返回值都一样,所以,能推导出来的东西都能简化,那么,更暴力的简化来了: 31 32 let numbers = [1, 3, 2, 4, 7, 8, 5] 33 let sortedNums = numbers.sorted (by: > ) //[8, 7, 5, 4, 3, 2, 1] 34 // 看出来什么了没有?没错,这里的简化不是放在闭包里面的。我的理解是,整个闭包等价于>函数,所以,可以把整个闭包替换成了>函数,举个例子: 35 36 let block: (Int, Int) -> (Int) = { $0 + $1 } 37 func testBlock(block: (Int, Int) -> (Int)) -> Int { 38 return block(1,3) 39 } 40 41 testBlock{ $0 + $1 } //3 42 testBlock(block: block) //3
4.尾随闭包(Trailing Closures)
If you need to pass a closure expression to a function as the function’s final argument and the closure expression is long, it can be useful to write it as a trailing closure instead. A trailing closure is written after the function call’s parentheses, even though it is still an argument to the function. When you use the trailing closure syntax, you don’t write the argument label for the closure as part of the function call.
如果函数的最后一个参数是闭包,可以使用尾随闭包代替,举个例子:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
}
// Here's how you call this function without using a trailing closure:
//没有使用尾随闭包的函数调用情况
someFunctionThatTakesAClosure(closure: {
// closure's body goes here
})
// Here's how you call this function with a trailing closure instead:
//使用了尾随闭包函数的调用情况
someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}
以数组的排序函数作为例子来看一下:
let numbers = [1, 3, 2, 4, 7, 8, 5]
let sortedNums = numbers.sorted(by: { $0 > $1 }) //没有使用尾随闭包,整个闭包写在sorted函数参数圆括号内,闭包内容多的话会显的很乱
let sortedNums = numbers.sorted() { $0 > $1 } //使用尾随闭包,这样会使代码看起来很整洁
swift里还有一个规则,如果函数只有闭包一个参数,作为尾随闭包,可以把()去掉,使代码更为简洁,代码如下:
let numbers = [1, 3, 2, 4, 7, 8, 5]
let sortedNums = numbers.sorted(){ $0 > $1 } //没有去掉"()"
let sortedNums = numbers.sorted { $0 > $1 } //去掉"()"
5.闭包捕获值(Capturing Values)
A closure can capture constants and variables from the surrounding context in which it is defined.
闭包可以捕获包裹它的上下文所定义的常量和变量。
(1)全局函数
var number = 0
var add = {
number += 1
print(number)
}
add() //1
add() //2
add() //3
print(number) //3
从上面的代码可以看出来,闭包捕获的是值的引用,当闭包内修改闭包外的值,闭包外的值也会跟着改变。
(2)函数嵌套函数
func makeIncrementer(from start: Int, amount: Int) -> ()->Int {
var number = start
return {
number += amount
return number
}
}
let incrementer = makeIncrementer(from: 0, amount: 1)
incrementer() //1
incrementer() //2
incrementer() //3
函数makeIncrementer返回的是一个没有参数返回整数的函数(闭包),所以,常量incrementer其实就是一个函数。每次调用incrementer()都会执行闭包里面的操作,而闭包的上下文就是makeIncrementer函数。从这也可以看出来,函数既能当返回值,也可以做参数,在swift妥妥的一等公民,比在Object-C的功能强大多了。
(3)swift中closure(闭包)和Object-C中block的区别
//block
NSInteger number = 1;
NSMutableString *str = [NSMutableString stringWithString: @"hello"];
void(^block)() = ^{
NSLog(@"%@--%ld", str, number);
};
[str appendString: @" world!"];
number = 5;
block(); //hello world!--1
//closure
var str = "hello"
var number = 1
let block = {
print(str + "--" + " (number)")
}
str.append(" world!")
number = 5
block() //hello world!--5
一句话来说,block内部会对值类型做一份复制,并不指向之前的值的内存地址,而对于对象类型则会指向对象当前的内存地址,所以修改number时,block里的number数值不变,而修改字符串时,block里的字符串则改变了;closure则默认给外部访问的变量加上了__block修饰词的block。至于闭包里的循环引用,可以看一下OC与Swift闭包对比总结这篇文章。
6.逃逸闭包(Escaping Closures)
A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns. When you declare a function that takes a closure as one of its parameters, you can write @escaping before the parameter’s type to indicate that the closure is allowed to escape.
逃逸闭包,指的是当一个函数有闭包作为参数,但是闭包的执行比函数的执行要迟。通俗来说,这个闭包的作用域本来是在当前函数里面的,然后它要逃出这个作用域,不想和函数同归于尽。那么闭包怎么逃逸呢?最简单的方法是把闭包赋值给外面的变量,举个例子:
var completionHandlers: [() -> Void] = []
//必须加上@escaping,不然编译会报错
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure {
print("hello")
} //函数执行完,不会打印"hello"
completionHandlers.first?() //打印"hello"
如果逃逸闭包访问的是类里面的成员,必须带上self来访问;如果访问的是全局的变量,则和非逃逸闭包一样。我的理解是,既然闭包逃逸了,则出了函数的作用域,则如果需要访问类里面的成员也找不到地址,因为函数已经被销毁,所以,需要带上类的地址self指针。
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
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"
7.自动闭包(Autoclosures)
An autoclosure is a closure that is automatically created to wrap an expression that’s being passed as an argument to a function. It doesn’t take any arguments, and when it’s called, it returns the value of the expression that’s wrapped inside of it. This syntactic convenience lets you omit braces around a function’s parameter by writing a normal expression instead of an explicit closure.
自动闭包,我理解是,没有参数,函数体只有返回值,没有多余的其他变量,举个例子:
let printStr = { "hello" }
print(printStr()) //hello
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
let customerProvider = { customersInLine.remove(at: 0) }
print("Now serving (customerProvider())!") // Prints "Now serving Chris!"
注意:要保证自动闭包里面代码能正确执行,比如,在执行customerProvider()之前把数组清空,那么执行customerProvider()会报错,代码如下:
customersInLine.removeAll()
customerProvider() //fatal error: Index out of range
自动闭包作为函数参数,不写"{}",直接写返回值:
1 var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] 2 3 //一般闭包 4 func serve(customer customerProvider: () -> String) { 5 print("Now serving (customerProvider())!") 6 } 7 serve(customer: {customersInLine.remove(at: 0)}) //Now serving Chris! 8 9 //自动闭包 10 func serve(customer customerProvider: @autoclosure () -> String) { 11 print("Now serving (customerProvider())!") 12 } 13 serve(customer: customersInLine.remove(at: 0)) //Now serving Chris!
逃逸的自动闭包:
var customersInLine = ["Barry", "Daniella"] // 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!"
8.总结
swift的闭包比Object-C的block功能强大很多,更简洁,更高效。而且,很多集合类型都集成了闭包,比如:map,flatMap等等。这些闭包的功能都很强大,也为swift添加了不少便利性。