闭包
闭包
闭包 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 1 节课,共 1 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 1 节课。
简介
您好,
闭包是自包含的代码块。闭包可以作为参数传递给代码,也可以在代码内部使用。
闭包是一种函数类型。可以通过在代码中传递它来使用。它们类似于其他编程语言中的匿名函数。
内存管理
最重要的一点是,闭包可以存储其定义环境中的常量和变量值。
这种存储称为闭包捕获。
Swift 会在存储过程中为我们管理内存。
闭包类型
闭包有以下几种类型。
- 全局函数是有名称的闭包,并且不会保存任何值。
- 嵌套函数可以携带其名称以及所封装函数的值。
- 闭包表达式则是匿名闭包,可以通过简单的语法存储其周围环境中的数据。
闭包语法
闭包的语法经过了尽可能的简化,其中最重要的特点如下。
- 可以推断闭包中的参数。
- 可以推断返回值。
- 可以在一行中返回闭包结果。
- 参数名称可以使用 $ 0 或 $ 1 等简写形式表示。
- 可以使用尾随闭包语法。
信息
闭包表达式指的是以内联方式编写闭包,重点在于语法和简洁性。
闭包语法
下面的示例代码展示了闭包语法。
{ (parameters) -> return type in
statements
}
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
//If we didn't use closures, our code structure would be as follows.
var reversedNames = names.sorted(by: backward)
闭包语法
不过,使用闭包时,我们不需要定义这种不必要的、带名称的全局闭包。
只需在参数中定义一个简单的闭包表达式即可。让我们来看一个示例。
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
let reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
print(reversedNames)
///sorted method takes closure as parameter.关键字
从这个关键字可以看出,闭包的主体从这里开始。
在开始闭包主体之前,必须定义闭包接收的参数和返回类型。
从上下文推断类型
从上下文推断类型
在上面的示例中,由于 sorted 方法以闭包形式传入,因此可以通过推断参数和返回类型来理解闭包表达式。
因此,我们不必额外定义它们。
在此示例中,由于 names 数组中的值是字符串,因此可以推断闭包接收的参数是字符串类型。
同样,相关闭包需要使用布尔类型进行排序,因此返回类型会被推断为布尔类型。
示例
这样可以简化下面的用法。
通过推断返回类型,也可以省略代码中的箭头。
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
let reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
print(reversedNames)简写语法
与简写语法相比,使用完整语法更易于避免代码阅读者产生困惑。
上面的语法还可以进一步缩短。
删除返回关键字后,可以使用闭包表达式缩短返回值的写法。
/*
In this example, the return keyword has been extracted.
It already has single expression.
There is no need to express it as an extra with return.
*/
var names = ["Coddy", "Kit"]
let reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
print(reversedNames)示例
在此示例中,返回关键字被省略了。
这里本来就只有一个表达式,因此不需要再通过返回关键字额外表示它。
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )名称简写
语法还可以进一步缩短。在上面的示例中,我们说明闭包接收两个参数(包括 s1 和 s2),它们的类型会被推断出来。
现在,我们可以使用简写形式表示这些参数,不必再费力定义它们。
此外,由于没有参数定义和返回值传递,也可以省略闭包主体起始关键字。
let names = ["Coddy", "Kit"]
let reversedNames = names.sorted(by: { $0 > $1 } )
print(reversedNames)
// In this example $0 will shortcut the first parameter and $1 will shortcut the second parameter.简写示例
var names = ["Coddy", "Kit"]
let reversedNames = names.sorted(by: >)
print(reversedNames)
// Greater than operator takes two parameters and the rotation type is squat.TRAILING CLOSURE
尾随闭包
如果传递给闭包函数的参数是函数的最后一个参数,并且闭包表达式较长,那么将其写成尾随闭包语句可能会更实用。
我们可以把尾随闭包当作一种语法糖来使用,以提高代码的可读性。
大多数 iOS 方法的最后一个参数都是闭包。
最后一个参数
例如,在此示例中,最后一个参数是闭包。
class Animator {
class func animate(withDuration duration: Double, animations: () -> Void) {
animations()
}
}
class MyViewController {
func start() {
// With the traditional method, we can call this fund as follows.
Animator.animate(withDuration: 1, animations: { [unowned self] in
print("red")
})
}
}
MyViewController().start()由于上面的代码可读性较差,可以写成如下形式
由于上面的代码可读性较差,可以写成如下形式。
import Foundation
Thread.sleep(forTimeInterval: 1)
print("Background color changed to red")示例
使用尾随闭包语法时,我们不必编写参数标签。
因为此时闭包已经成为调用表达式的一部分。
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
}
// Here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure(closure: {
// closure's body goes here
})
// Here's how you call this function with a trailing closure instead:
someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}语法
实际上,我们可以把尾随闭包语法看作一种语法糖。它可以让代码更易读。
/*
In the example below, animations
are expressed as closure.
*/
public class func animate(withDuration: TimeInterval, animations: () -> Void)
// In traditional usage, we need to make a call like the following.
UIView.animate(withDuration: 1, animations: { [unowned self] in
self.view.backgroundColor = UIColor.red
})
/*
In this way, both the readability of the code
increases and we avoid the use of extra brackets.
If it is as above and the last parameter of the method
is closure, Swift allows to make a call like the one below.
*/
UIView.animate(withDuration: 1) { [unowned self] in
self.view.backgroundColor = UIColor.red
}我们可以使用下面的代码测试代码
我们可以使用下面的代码测试代码。
func greetThenRunClosure(name: String, closure: () -> ()) {
print("Hello, \(name)!")
closure()
}
greetThenRunClosure(name: "Paul") {
print("The closure was run")
}闭包捕获值
闭包捕获值
闭包可以保存包围其环境的变量值。您甚至可以在闭包自身的主体中修改这些变量的值,并访问它们。
即使这些变量已经不在其定义时所在的原始作用域中,也仍然可以如此。
补充
通常,这些数据可以与闭包表达式一起保存在嵌套函数中。它们可以存储外部函数的数据。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
print(incrementByTen())
// returns a value of 10
print(incrementByTen())
// returns a value of 20
print(incrementByTen())
// returns a value of 30续
如果将闭包赋值为类的属性,它就会开始持有该实例对闭包的引用。
因此,闭包与实例之间会建立强引用循环。
应特别注意内存泄漏问题。
警告
闭包和函数都是引用类型。
@escaping
在许多异步使用闭包的场景中,我们可能需要这种用法。
在早期版本的 Swift(1 和 2)中,Swift 参数中的闭包是逃逸的,后来变成了非逃逸闭包。如果需要传递非逃逸闭包,就必须明确指定。
在后续版本中,为了节省内存,闭包参数默认被设置为非逃逸。
现在,如果希望将闭包定义为逃逸闭包,就必须使用 @escaping 关键字。
@ESCAPING 关键字
@ESCAPING 关键字
如果希望能够在函数返回后调用分配给函数的闭包,就需要使用 @escaping 关键字进行标记。
默认定义为函数参数的闭包是引用类型,但不会增加引用计数。因为调用时闭包的生命周期就结束了,我们不需要额外增加引用计数。
不过,如果函数调用完成后仍需要使用作为参数传入的闭包,就可能需要使用 @escaping 关键字进行声明。
nonEscaping
首先,让我们看看 nonEscaping 闭包是如何工作的。
当这个闭包作为参数传递给函数时,闭包会在函数体内执行,然后返回给编译器。
函数返回后,也就是执行完成后,它就会离开闭包作用域,并被释放回内存。
使用逃逸闭包
使用逃逸闭包
当闭包函数作为参数传递时,即使闭包函数返回且执行完成,它仍会继续被保留。
它会占用内存空间,直到闭包执行为止。逃逸闭包有多种类型。
恭喜您
恭喜您 🎉
本课即将结束。
下一课再见 😎

常见问题解答
「闭包」课时是免费的吗?
是的 — 「闭包」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Swift Academy 课程的其余内容,请升级到 CoddyKit PRO。 Swift Academy 课程共包含 1 节课。
「闭包」这节课中我会学到什么?
闭包 你通过在浏览器中直接运行的动手代码来练习 Swift Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Swift Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Swift Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 1 节。
「闭包」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Swift Academy 课中编写并运行代码吗?
能。每节 Swift Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。