Swift教程(七)--闭包

2019-07-31 10:00:02 浏览数 (1)

本次的教程是基于Swift5.1版本

闭包是可以在你的代码中被传递和引用的功能性独立模块。Swift 中的闭包和 C 以及 Objective-C 中的 blocks 很像,还有其他语言中的匿名函数也类似。

闭包能够捕获和存储定义在其上下文中的任何常量和变量的引用,这也就是所谓的闭合并包裹那些常量和变量,因此被称为“闭包”,Swift 能够为你处理所有关于捕获的内存管理的操作。

在函数章节中有介绍的全局和内嵌函数,实际上是特殊的闭包。闭包符合如下三种形式中的一种:

  • 全局函数是一个有名字但不会捕获任何值的闭包;
  • 内嵌函数是一个有名字且能从其上层函数捕获值的闭包;
  • 闭包表达式是一个轻量级语法所写的可以捕获其上下文中常量或变量值的没有名字的闭包。

闭包表达式语法

闭包表达式语法有如下的一般形式:

代码语言:javascript复制
{ (parameters) -> (return type) in
statements
}

尾随闭包

如果你需要将一个很长的闭包表达式作为函数最后一个实际参数传递给函数,使用尾随闭包将增强函数的可读性。尾随闭包是一个被书写在函数形式参数的括号外面(后面)的闭包表达式:

代码语言:javascript复制
func trailingClosure(a:Int,closure:(Int)->Void){
closure(a)
}

//调用一:常规的函数调用形式
trailingClosure(a: 11, closure: {(b:Int)->Void in
print(b)
})
//输出:11

//调用二:尾随闭包的调用形式
trailingClosure(a: 22) { (b:Int) in
print(b)
}
//输出:22

如果闭包表达式被用作函数唯一的实际参数并且你把闭包表达式用作尾随闭包,那么调用这个函数的时候你就不需要在函数的名字后面写一对圆括号 ( )。

捕获值

一个闭包能够从上下文捕获已被定义的常量和变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍能够在其函数体内引用和修改这些值。

在 Swift 中,一个能够捕获值的闭包最简单的模型是内嵌函数,即被书写在另一个函数的内部。一个内嵌函数能够捕获外部函数的实际参数并且能够捕获任何在外部函数的内部定义了的常量与变量

这里有个命名为 makeIncrement 的函数栗子,其中包含了一个名叫 incrementer 一个内嵌函数。这个内嵌 incrementer() 函数能在它的上下文捕获两个值, runningTotal 和 amount 。在捕获这些值后,通过 makeIncrement 将 incrementer作为一个闭包返回,每一次调用 incrementer 时,将以 amount作为增量来增加 runningTotal :

代码语言:javascript复制
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal  = amount
return runningTotal
}
return incrementer
}

当我们单看这个函数时,会发现内嵌函数 incrementer()不同寻常:

代码语言:javascript复制
func incrementer() -> Int {
runningTotal  = amount
return runningTotal
}

incrementer() 函数是没有任何形式参数, runningTotal 和 amount 不是来自于函数体的内部,而是通过捕获主函数的 runningTotal 和 amount 把它们内嵌在自身函数内部供使用。当调用 makeIncrementer 结束时通过引用捕获来确保不会消失,并确保了在下次再次调用 incrementer 时, runningTotal 将继续增加。

这有个使用 makeIncrementer 的例子:

代码语言:javascript复制
let incrementByTen = makeIncrementer(forIncrement: 10)

这个例子定义了一个叫 incrementByTen 的常量,该常量指向一个每次调用会加 10 的函数。调用这个函数多次得到以下结果:

代码语言:javascript复制
incrementByTen()
//return a value of 10

incrementByTen()
//return a value of 20

incrementByTen()
//return a value of 30

如果你建立了第二个 incrementer ,它将会有一个新的、独立的 runningTotal 变量的引用:

代码语言:javascript复制
let incrementBySeven = makeIncrementer(forIncrement: 7)

incrementBySeven()
// returns a value of 7

闭包是引用类型

在上面例子中, incrementBySeven 和 incrementByTen 是常量,但是这些常量指向的闭包仍可以增加已捕获的变量 runningTotal 的值。这是因为函数和闭包都是引用类型。

无论你什么时候赋值一个函数或者闭包给常量或者变量,你实际上都是将常量和变量设置为对函数和闭包的引用。这上面这个例子中,闭包选择 incrementByTen 指向一个常量,而不是闭包它自身的内容。

逃逸闭包

当闭包作为一个实际参数传递给一个函数的时候,我们就说这个闭包逃逸了,因为它可以在函数返回之后被调用。当你声明一个接受闭包作为形式参数的函数时,你可以在形式参数前写 @escaping 来明确闭包是允许逃逸的。

闭包可以逃逸的一种方法是被储存在定义于函数外的变量里。比如说,很多函数接收闭包实际参数来作为启动异步任务的回调。函数在启动任务后返回,但是闭包要直到任务完成——闭包需要逃逸,以便于稍后调用。让闭包 @escaping 意味着你必须在闭包中显式地引用 self。

代码语言:javascript复制
var escapings:[()->Void] = []

func escapingFun(a: @escaping ()->Void){
escapings.append(a)
}

闭包就是一种匿名函数,可以把它理解成一个代码块(一个没有名称的函数块),然后定义了一个变量去指向代码块的地址,在合适的地方通过这个变量去执行这个代码块。

0 人点赞