闭包和闭包表达式
- 闭包是可以在你的代码中杯传递和引用的功能性独立代码块
- 闭包能够捕获和储存定义再起上下文中任何常量和变量的引用,这也就是所谓的闭合并包裹哪些常量和变量,因此被称为“闭包”,Swift 能够为你处理所有关于捕获的内存管理的操作
- 闭包的概念
- 在函数中的全局和内嵌函数,实际上是特殊的闭包,闭包符合如下三种行驶中的一种:
- 全局函数是一个有名字,但不会捕获任何值的闭包
- 内嵌函数时一个有名字,且能从其上层函数捕获值的闭包
- 闭包表达式是一个轻量级语法所写的,可以捕获其上下文中常量或变量值的没有名字的闭包
- 闭包表达式
- 闭包表达式是一种在简短行内就能写完的闭包的语法
- 闭包表达式-从sorted 函数说起
- Swift 的标准库提供了一种叫做 sorted(by:) 的方法,会根据你提供的排序闭包将已知类型的数组的值进行排序,一旦它排序完成, sorted(by:) 方法会返回与原数组类型大小完全相同的一个新数组,该数组的元素是已排序好的,原始数组不会被sorted(by:) 方法修改
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward(_:_:))
print(reversedNames)
- 闭包表达式语法
- 闭包表达式语法能够使用常量形式参数、变量形式参数和输入输出形式参数,但不能提供默认值。可变形式参数也能使用,但需要在形式参数列表的最后面使用。元组也可被用来作为形式参数和返回类型
{ (parameters) -> (return type) in
statements
}
- 闭包表达式语法版本的 backward
- 将之前 backward(::) 函数改为闭包表达版本
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted { (s1: String, s2: String) -> Bool in
return s1 > s2
}
print(reversedNames)
- 从语境中推断类型
- 因排序闭包为实际参数来传递给函数,故Swift能推断它的形式参数类型和返回类型
- sorted(by:) 方法期望它的形式参数是一个 (String, String) -> Bool 类型的函数,这意味着 (String, String) 和 Bool 类型不需要被写成闭包表达式定义中的一部分,因为所有的类型都能被推断,返回箭头 (->) 和围绕在形式参数名周围的括号也能被省略
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted(by: { s1, s2 in return s1 > s2})
print(reversedNames)
- 从单表达式闭包隐式返回
- 单表达式闭包能够从他们的声明中删除return 关键字,来隐式返回他们单个表达式的结果
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted(by: { s1, s2 in s1 > s2})
print(reversedNames)
- 简写实际参数名
- Swift自动对行内闭包提供简写实际参数,可以通过 1, $2 等名字来引用闭包的实际参数值
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted(by: { $0 > $1})
print(reversedNames)
- 运算符函数
Swift 的 String类型定义了关于大于号(>)的特定字符串实现,让其作为一个由两个 String 类型形式参数的函数并返回一个 Bool 类型的值,这正好与sorted(by:) 方法的形式参数需要的函数相匹配,因此,你能简单的传递一个大于号,并且Swift 将推断你想使用大于号 特殊字符串函数实现
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted(by: >)
print(reversedNames)
- 尾随闭包
- 如果你需要讲一个很长的闭包表达式作为函数最后一个实际参数传递给函数,使用尾随闭包将增强函数的可读性,尾随闭包是一个被书写在函数形式参数的括号外边(后面)的闭包表达式
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted{ $0 > $1}
print(reversedNames)
闭包捕获值
- 捕获值
- 一个闭包能够从上下文捕获一杯定义的常量和变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍能够在其函数体内引用和修改这些值。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
- 作为一种优化,如果一个值没有改变或者在闭包的外边,Swift可能会使用这个值的拷贝而不是捕获
- Swift 也处理了变量的内存管理操作,当遍历不再需要时被释放
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
print(runningTotal)
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
incrementByTen()
incrementByTen()
4.如果你建立了第二个incrementer,它将会有一个新的、独立的runningTotal变量的引用
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
print(runningTotal)
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
incrementByTen()
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
incrementByTen()
- 闭包是引用类型
- 在 Swift 中,函数和闭包都是引用类型
- 无论你什么时候赋值一个函数或者闭包给常量或者变量,你实际上都是讲常量和变量设置为对函数和闭包的引用
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
print(runningTotal)
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
incrementByTen()
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
incrementByTen()
let also = incrementByTen
also()
- 闭包是引用类型
- 如果你分配了一个闭包给类实例的属性,并且闭包通过引用该实例或者它的成员来捕获实例,你将在闭包和实例间会产生循环引用。
逃逸闭包和自动闭包
- 逃逸闭包
- 当闭包作为一个实际参数传递给一个函数的时候,并且它会在函数返回值后调用,我们就说这个闭包逃逸了。当你声明一个接受闭包作为形式参数的函数时,你可以在形式参数前些 @escaping 来明确闭包是允许逃逸的
- 闭包可以逃逸的一种方法是被存储定义与函数歪的变量里,比如说,很多函数接受闭包实际参数来作为启动异步任务的回调,函数在启动任务后返回,但是闭包要知道任务完成--闭包需要逃逸,一遍稍后调用
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
- 让闭包 @escaping 意味着你必须在比闭包中显示地引用 self
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithNonescapingClosure {
self.x = 100
}
someFunctionWithNonescapingClosure {
x = 200
}
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
completionHandlers.first
print(instance.x)
- 自动闭包
- 自动闭包是一种自动创建的用来把作为实际参数传递给函数的表达式打包的闭包,它不接受任何实际参数,并且当它被调用时,它会返回内部打包的表达式的值
- 这个语法的好处在于通过写普通表达式,代替显式闭包而使你省略保卫函数形式参数的括号
- 自动闭包允许你延迟处理,因此闭包内部的代码知道你调用它的时候才会运行,对于有副作用或者暂用资源的代码来说很有用,因为它可以允许你控制diamante何时进行求值
var customersInLine = ["one", "two", "three", "four", "five"]
print(customersInLine.count)
let customerProvider = { customersInLine.remove(at: 0)}
print(customersInLine.count)
print("Now serving \(customerProvider())!")
print("Now serving \(customerProvider())!")
print(customersInLine.count)
- 当你传一个闭包作为实际参数到函数的时候,你会得到与延迟处理相同的行为
var customersInLine = ["one", "two", "three", "four", "five"]
print(customersInLine.count)
func serve(customer customerProvider: () -> String ) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) })
print(customersInLine.count)
- 通过 @ autoclosure 标志标记它的形式参数使用了自动闭包,现在你可以调用函数就像它接受了一个String 实际参数而不是闭包,实际参数自动地转换为闭包,因为 CustomerProvider形式参数的类型被标记为 @autoclosure标记
var customersInLine = ["one", "two", "three", "four", "five"]
print(customersInLine.count)
func serve(customer customerProvider: @autoclosure () -> String ) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
print(customersInLine.count)
- 自动+逃逸
- 如果你想要自动闭包允许逃逸,就同时使用 @autoclosure 和 @escaping 标志
var customersInLine = ["one", "two", "three", "four", "five"]
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.")
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}