案例代码下载
闭包
函数闭包可以从定义它们的上下文中捕获和存储对任何常量和变量的引用,Swift处理捕获的所有内存管理。闭包包括以下三种形式之一:
- 全局函数是具有名称但不捕获任何值的闭包。
- 嵌套函数是具有名称的闭包,可以从其封闭函数中捕获值。
- Closure表达式是一种未命名的闭包,用轻量级语法编写,可以从上下文中捕获值。
Swift的闭包表达式具有干净,清晰的风格,闭包的优势包括:
- 从上下文中推断参数和返回值类型
- 单表达式闭包的隐式返回
- 速记参数名称
- 尾随闭包语法
闭包表达式
闭包表达式语法
Closure表达式语法具有以下一般形式:
{ (parameters) -> return type in
statements
}
闭包的主体的开头由in关键字引入。这个关键字表示闭包的参数和返回类型的定义已经完成,闭包的主体开始:
var intArray = [7, 1, 4, 5, 9, 10, 6, 3, 8, 2]
intArray.sort(by: { (a, b) -> Bool in
return a < b
})
print(intArray)
从上下文中推断类型
闭包作为参数传递给方法,Swift可以推断出它的参数类型以及它返回的值的类型,因此参数必须是一个类型的函数。这意味着不需要将类型作为闭包表达式定义的一部分来编写。因为可以推断出所有类型,所以也可以省略返回箭头(->)和参数名称周围的括号:
intArray.sort(by: { a, b in
return a < b
})
在将闭包作为内联闭包表达式传递给函数或方法时,始终可以推断出参数类型和返回类型。因此,当闭包用作函数或方法参数时,永远不需要以最完整的形式编写内联闭包。
单表达式闭包的隐式返回
单表达式闭包可以省略return关键字来隐式返回单个表达式的结果:
intArray.sort(by: { a, b in
a < b
})
速记参数名称
Swift自动提供内联闭包速记参数名,它可以使用的名称,指的是闭包的参数值1,$2,等等。
在闭包表达式中使用这些简写参数名称,则可以从其定义中省略闭包的参数列表,并且将从期望的函数类型推断缩写参数名称的数量和类型。in关键字也可以被省略,因为闭包表达是由完全其自身的:
intArray.sort(by: { $0 < $1 })
运算符方法
有一种更短的方式来编写闭包表达式,Swift将小于运算符实现为具有两个Int类型的参数的方法,并返回Bool类型值。这与sorted(by:)方法所需要的参数类型是相符合的,所以可以简单的使用一个小于符号,Swift将推断出其实现:
intArray.sort(by: <)
尾随闭包
如果需要将闭包表达式作为函数的最后一个参数传递给函数,并且闭包表达式很长,则将其写为尾随闭包可能很有用。在函数调用的括号之后写入尾随闭包,即使它仍然是函数的参数。使用尾随闭包语法时,不要将闭包的参数标签写为函数调用的一部分。
intArray.sort() <
如果提供闭包表达式作为函数或方法的唯一参数,并且将该表达式作为尾随闭包提供,则在调用函数时,不需要在函数或方法的名称后面写一对括号():
intArray.sort { $0 < $1 }
捕捉值
闭包可以从定义它的周围上下文中捕获常量和变量。然后闭包可以引用并修改其体内的常量和变量的值,即使定义常量和变量的原始范围不再存在。
捕获值的最简单形式的闭包是嵌套函数,写在另一个函数体内。嵌套函数可以捕获其外部函数的任何参数,还可以捕获外部函数中定义的任何常量和变量。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
作为优化,如果值在闭包内没有发生改变,那么闭包会捕获并存储值的副本。在闭包不需要变量时,Swift处理变量所涉及的内存管理。
注意: 如果为类实例的属性分配闭包,并且闭包通过引用实例或其成员来捕获该实例,则将在闭包和实例之间创建一个强引用循环。
let incrementByTen = makeIncrementer(forIncrement: 10)
let incrementBySeven = makeIncrementer(forIncrement: 7)
print(incrementByTen())
print(incrementByTen())
print(incrementByTen())
print(incrementByTen())
print(incrementBySeven())
/*
打印结果:
10
20
30
40
7
*/
闭包是引用类型
无论何时将函数或闭包赋值给常量或变量,实际上都是将该常量或变量设置为对函数或闭包的引用。也意味着如果为两个不同的常量或变量分配闭包,那么这两个常量或变量都引用相同的闭包。
let otherIncrementByTen = incrementByTen
print(otherIncrementByTen())
print(otherIncrementByTen())
/*
打印结果:
50
60
*/
逃逸闭包
逃逸闭包是指当闭包作为参数传递给函数,但在函数返回之后被调用的闭包。当声明一个以闭包作为其参数之一的函数时,可以在参数的类型之前写入@escaping,以指示允许闭包逃逸。
闭包可以逃逸的一种方法是存储在函数外部定义的变量中:
var completions = [() -> Void]()
func escapingClosureFunc(completion: @escaping () -> Void) {
completions.append(completion)
}
逃逸闭包需要显式的引用self:
func nonescapingClosureFunc(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 0
func someThing() {
escapingClosureFunc {
self.x = 100
}
nonescapingClosureFunc {
x = 200
}
}
}
let instance = SomeClass()
instance.someThing()
print(instance.x)
completions.first!()
print(instance.x)
/*
打印结果:
200
100
*/
Autoclosures
autoclosure是自动创建被作为参数传递给函数的表达式的闭包。它不接受任何参数,当它被调用时,它返回包含在表达式中的值。这种语法方便使通过编写普通表达式而不是显式闭包来省略函数参数周围的大括号。
autoclosure允许延迟执行,因为在调用闭包之前,内部代码不会运行。延迟执行对于具有副作用或计算成本高昂的代码非常有用,因为它可以控制何时执行该代码。
var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
func autoclosureFunc(closure: @escaping @autoclosure () -> Int) -> () -> Int {
return closure
}
print(array.count)
let closure = autoclosureFunc(closure: array.removeFirst())
print(array.count)
closure()
print(array.count)
/*
打印结果:
10
10
9
*/
网友评论