Swift学习笔记
根据《Swift编程权威指南》和 Swift官方文档 学习整理的Swift学习笔记,持续更新,中间有什么错误,欢迎大家留言讨论。
系列总目录
官方文档
文章目录
五、闭包
闭包(closure)是在应用中完成特定任务的互相分离的功能组。闭包本质上是内联函数,获取传递给另一个函数的函数作为一个参数并且直接声明,而不是在某个地方单独声明。
类似Java和Kotlin的lambda
表达式。(java的lambda表达式也可以称为闭包)
定义函数的两种方式:在Swift中,可以通过func
定义一个函数,也可以通过闭包表达式定义一个函数:
func sum(_ v1: Int, _ v2: Int) -> Int {
v1 + v2
}
1、闭包表达式
闭包表达式语法具有以下一般形式:
{ (
parameters
) ->return type
in
statements
}
var fn = {
(v1: Int, v2: Int) -> Int in
return v1 + v2
}
注意,闭包的参数类型和返回类型与函数sum(_:_:)
声明相同,都是:(v1: Int, v2: Int) -> Int
。
闭包主体的开头由in
关键字引入。该关键字的出现代表闭包的参数和返回类型的定义已完成,并且闭包的主体即将开始。
示例 — 对数组进行排序:sorted(by:)
方法接受一个闭包,该闭包采用两个与数组内容相同类型的参数,并返回一个Bool
值,以说明对这些值进行排序后,第一个值应出现在第二个值之前还是之后。
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
改为闭包表达式:
var reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
2、闭包表达式的简写
上述代码可以继续进行以下重构:
2.1 利用类型推断
var reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
由于排序闭包是作为方法的参数传递的,因此Swift可以推断其参数的类型以及它返回的值的类型。
2.2 隐式返回单表达式闭包
var reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
单表达式闭包可以隐式返回单个表达式的结果,可以在声明中省略return
关键字。
2.3 利用参数的快捷语法
var reversedNames = names.sorted(by: { $0 > $1 } )
闭包表达式可以利用快捷参数语法,就不需要像之前声明s1和s2那样显式声明参数了。编译器知道闭包参数的类型是正确的,也知道基于>运算符能推断出什么。
2.4 直接使用操作符
var reversedNames = names.sorted(by: >)
只需传递大于号运算符,Swift就会推断出想要使用其特定于字符串的实现。
3、尾随闭包
如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性。
尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式。
var reversedNames = names.sorted() { $0 > $1 }
如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要在函数名后边写圆括号()
:
var reversedNames = names.sorted { $0 > $1 }
总结:比较一下之前代码
var reversedNames = names.sorted(by: { $0 > $1 } ) // 快捷语法简化闭包表达式
var reversedNames = names.sorted() { $0 > $1 } // 尾随闭包
var reversedNames = names.sorted { $0 > $1 } // 尾随闭包,省略()
4、忽略参数
func exec(fn: (Int, Int) -> Int) {
print(fn(1,2))
}
exec() {
_,_ in 10
}
无论传任何数,都返回10,两个参数没有用了,可以使用 _
忽略。
5、闭包与捕获
- 一个函数和它所捕获的变量\常量环境组合起来,称为闭包。
- 一般指定义在函数内部的函数
- 一般它捕获的是外层函数的局部常量\变量
在Swift中,最简单的可以捕获值的闭包形式是嵌套函数,它写在另一个函数的主体内。嵌套函数可以捕获其外部函数的任何自变量,还可以捕获在外部函数内定义的任何常量和变量。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
incrementer()
函数没有任何参数,但是它是在函数体内引用runningTotal
和引用的amount
。makeIncrementer()
函数定义了一个名为incrementer()
的嵌套函数,它执行实际的增量操作。这个函数只是将amount
添加到runningTotal
中,并返回结果。
下面使用此函数:本示例设置一个常量,incrementByTen
以引用一个增量函数,该常量在每次调用时都会添加10
到其runningTotal
变量中。多次调用该函数可显示此行为:
let incrementByTen = makeIncrementer(forIncrement: 10)
print(incrementByTen()) // 10
print(incrementByTen()) // 20
print(incrementByTen()) // 30
如果创建第二个增量器:
let incrementBySeven = makeIncrementer(amount: 7)
print(incrementBySeven()) // 7
print(incrementByTen()) // 40
- 可以把闭包想象成是一个类的实例对象
- 内存在堆空间
- 捕获的局部变量\常量就是对象的成员(存储属性)
- 组成闭包的函数就是类内部定义的方法
6、闭包是引用类型
在上面例子中,incrementByTen
和incrementBySeven
是常量,但是这些常量所引用的闭包仍然能够增加它们所捕获的runningTotal
变量。闭包是引用类型(reference type)。这意味着当把函数赋给常量或变量时,实际上是在让这个常量或变量指向这个函数,并没有为这个函数创建新的副本。
这也意味着,如果将闭包分配给两个不同的常量或变量,则这两个常量或变量都引用同一闭包。
7、自动闭包
自动闭包是一种自动创建的闭包,它可以包装作为参数传递给函数的表达式。它不接收任何参数,当它被调用时,将返回包装在其中的表达式的值。这种语法的便利性允许你通过编写普通表达式而不是显示闭包来省略函数参数周围的大括号。
调用带有自动闭包的函数很常见,但实现这种函数并不常见。自动闭包可让您延迟评估,因为在调用闭包之前,内部代码不会运行。延迟评估对于具有副作用或计算量大的代码很有用,因为它使您可以控制何时评估该代码。
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// 5
let customerProvider = { customersInLine.remove(at: 0) } // 闭包
print(customersInLine.count)
// 5
print("Now serving \(customerProvider())!") // 调用闭包
// Now serving Chris!
print(customersInLine.count)
// 4
即使customersInLine
数组的第一个元素已由闭包中的代码删除,但只有在实际调用闭包时才删除数组元素。如果从不调用闭包,则闭包内部的表达式不会被求值,这意味着数组元素不会被删除。
当传递一个闭包作为函数参数时,也可以达到延迟计算的目的。
// 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!"
上面示例中的serve(customer:)
函数采用显式闭包,返回客户的名称。下面示例中的serve(customer:)
函数执行相同的操作,但不是采用显式闭包,而是通过使用@autoclosure
属性标记其参数类型来采用自动闭包。
// 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!"