闭包基础
** 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似 **
如果要使用闭包,首先要闭包赋值给变量或者常量
定义一个可以持有闭包的变量
var multiplyClosure: (Int, Int) -> Int
将闭包赋值给前面定义的变量
multiplyClosure = { (a: Int, b: Int) -> Int in
return a * b
}
使用定义好的闭包
let result = multiplyClosure(4, 2)
// result is 8
闭包函数的简易写法
- 如果闭包只声明了一个返回值,那么可以将return省略掉
multiplyClosure = { (a: Int, b: Int) -> Int in
a*b
}
- 可以省略所有的类型信息,因为前面定义的变量中已经指明了闭包的参数类型和返回值类型
multiplyClosure = { (a, b) in
a*b
}
- 省略掉所有参数列表。闭包中可以通过$加一个从0开始的数字来表示参数。
不过这种写法个人认为可读性不强,不建议这么写
multiplyClosure = {
$0 * $1
}
下面来通过一个例子来简单的使用一下这几种写法
首先定义一个函数,其中第三个参数是一个函数
func operateOnNumbers(_ a: Int, _ b: Int, operation: (Int,Int) -> Int) -> Int{
let result = operation(a, b)
print(result)
return result
}
接着调用这个函数
let addClosure = { (a: Int, b: Int) in //定义一个闭包
a+b
}
operateOnNumbers(4, 2, operation: addClosure)
当然,operation这个参数,也可以直接传函数,传闭包是因为闭包也是没有名称的函数,对于operateOnNumbers来说,调用闭包或者函数对它来说没有区别
func addFunction(_ a: Int, _ b: Int) -> Int {
return a + b}
operateOnNumbers(4, 2, operation: addFunction)
下面来看几种简易的写法
operateOnNumbers(3, 4, operation: {$0 * $1}) //上文的第三种简写
operateOnNumbers(3, 4, operation: {(a,b) in
a * b //第二种,省略类型的简写
})
operateOnNumbers(3, 4, operation: {
(a: Int, b: Int) -> Int in //第一种省略return
a * b
})
operateOnNumbers(3, 4, operation: * ) //最简易的写法
尾随闭包
operateOnNumbers(3, 4){
$0 * $1 // 这种简写比较特殊,只有当闭包是作为函数的最后一个参数的时候才可以这么用
}
定义无参数无返回值的闭包
let voidClosure: () -> Void = {
print("Swift Apprentice is awesome!")}
voidClosure()
闭包--值捕获
闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
swift中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
incrementer闭包可以捕获makeIncrementer(forIncrement:)中的runningTotal和amount变量的引用,捕获引用保证了runningTotal和amount变量在调用完makeIncrementer后不会消失,并且保证了在下一次执行incrementer函数时,runningTotal依旧存在。
let incrementByTen = makeIncrementor(forIncrement: 10)
incrementByTen()// 返回的值为10
incrementByTen()// 返回的值为20
incrementByTen()// 返回的值为30
let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven()// 返回的值为7
incrementByTen()// 返回的值为40
可以看到incrementByTen中的变量和incrementBySeven中的变量没有任何联系,而且,他们分别保持着对变量的引用。
闭包是引用类型
** 函数和闭包都是引用类型 **
无论你讲函数或闭包赋值给一个常量还是变量,实际上都是讲常量或变量的值设置为对应的函数或闭包的引用。 上面的例子中,指向闭包的引用incrementByTen是一个常量,而并非闭包内容本身。
如果将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包:
let alsoIncrementByTen = incrementByTenalso
IncrementByTen()// 返回的值为50
逃逸闭包(Escaping Closures)
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,称闭包从函数中逃逸。
当定义接受闭包作为参数的函数时,可以在参数名之前标注@escaping,用来指明这个闭包是允许“逃逸”出这个函数的。
一种能使闭包逃逸出函数的方法是,将这个闭包保存在函数外部定义的变量中。
例如:很多启动异步操作的函数接受一个闭包参数作为completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包知道异步操作结束后才会被调用。
这种情况,闭包需要逃逸出函数,因为闭包需要在函数返回之后被调用。
var completionHandlers: [() -> Void] = []func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
如果不加@escaping关键字,将会得到一个编译错误
将一个闭包标记为@escaping意味着你必须在闭包中显示地引用self
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void){
completionHandlers.append(completionHandler)
}
func someFunctionWithNoneEscapingClosure(closure: () -> Void){
closure()
}
class SomeClass{
var x = 10
func doSomething() {
someFunctionWithEscapingClosure {
self.x = 100
}
someFunctionWithNoneEscapingClosure {
x = 200
}
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x) //打印出200
completionHandlers.first?()
print(instance.x) //打印出100
自动闭包
自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。
自动闭包能够延迟求职,直到调用这个闭包,代码段才回被执行。
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())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出 "4"
只有当闭包customerProvider执行时,才会执行remove操作
利用闭包来自定义排序方法
数组的排序
let names = ["ZZZZZZ", "BB", "A", "CCCC", "EEEEE"]names.sorted()
// ["A", "BB", "CCCC", "EEEEE", "ZZZZZZ"]
可以通过制定闭包来为数组指定一个新的排序方式
names.sorted {
$0.characters.count > $1.characters.count}
// ["ZZZZZZ", "EEEEE", "CCCC", "BB", "A"]
用闭包来遍历集合
集合实现了很多很实用的功能,比如排序,筛选等等。
这些功能都配合闭包实用,来定义不同的规则
var prices = [1.5, 10, 4.99, 2.30, 8.19]
let largePrices = prices.filter {
return $0 > 5
}
利用闭包配合map方法,可以将数组里面的每个值,进行指定的操作,并返回一个新的数组
let salePrices = prices.map {
return $0 * 0.9}
let sum = prices.reduce(0) {
return $0 + $1
}
同样的,也可以对字典进行对应的操作
let stock = [1.5:5, 10:2, 4.99:20, 2.30:5, 8.19:30]let stockSum = stock.reduce(0) {
return $0 + $1.key * Double($1.value)}
字典遍历的时候返回的是一个元组,所以可以分别拿到key和value进行操作
举个例子来说明下上面几个集合的功能函数配合闭包的用法
let namesAndAges = ["Owen": 40, "Pogba": 21, "Martial": 21, "Rashford": 19, "Marry": 17, "Lina": 15]
// 使用reduce将数组中的名字连成一串
names.reduce(""){
return $0 + $1
}
//将上面的数组,先筛选出所有名字超过4个字符的,然后将数组中的名字连成一串
let filteredNames = names.filter{
return $0.characters.count > 4
}
filteredNames.reduce(""){
$0 + $1
}