在 Swift 中,我们可以使用 @escaping
关键字来定义逃逸闭包。逃逸闭包是指闭包在函数返回后才被调用的闭包。这种闭包通常作为异步API的回调参数,用于在异步操作完成后执行一些代码。
逃逸闭包与非逃逸闭包
闭包作为函数的参数可以是被传递和使用的。当一个闭包被定义为函数的参数时,它默认为非逃逸闭包,即它必须在函数执行结束前调用。但是,当一个闭包在函数返回后仍然被调用时,我们称之为逃逸闭包
使用场景
异步API的回调参数
例如,在使用URLSession
进行网络请求时,我们可以使用逃逸闭包来处理异步请求的结果:
func loadData(completionHandler: @escaping (Data?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
completionHandler(data, error)
}
task.resume()
}
延迟执行的操作
例如,我们可以使用逃逸闭包来实现一些延迟执行的操作,例如在一些情况下我们需要在弹出视图控制器之后执行一些操作:
class MyClass {
// 默认为逃逸闭包
var completionHandler: (() -> Void)?
func doSomething() {
// 异步操作
completionHandler?()
}
}
let myObject = MyClass()
myObject.completionHandler = {
print("操作完成")
}
myObject.doSomething()
在这个例子中,我们将定义一个completionHandler
闭包,该闭包将在视图控制器被弹出后执行。我们可以使用逃逸闭包来完成这个功能。
当一个闭包被定义为函数的参数时,默认为非逃逸闭包
当一个闭包被定义为某个类的变量时,默认为逃逸闭包
定时器
有时候,我们需要开启一个定时器,然后在定时器结束后执行某些操作。使用逃逸闭包可以方便地实现这样的需求。例如:
class TimerManager {
func startTimer(duration: TimeInterval, completionHandler: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
completionHandler()
}
}
}
let timerManager = TimerManager()
timerManager.startTimer(duration: 2) {
print("定时器结束!")
}
在这个例子中,我们定义了一个TimerManager
类,并在其中声明了一个逃逸闭包completionHandler
。
当我们调用startTimer
方法时,它会开启一个计时器并在指定的时间后调用completionHandler
动画
当我们需要在一段时间内执行一些动画操作时,可以使用逃逸闭包完成。例如:
class AnimationManager {
func animate(duration: TimeInterval, completionHandler: @escaping () -> Void) {
UIView.animate(withDuration: duration) {
// 执行动画
} completion: { finished in
completionHandler()
}
}
}
let animationManager = AnimationManager()
animationManager.animate(duration: 1) {
print("动画结束!")
}
在这个例子中,我们定义了一个AnimationManager
类,并在其中声明了一个逃逸闭包completionHandler
。当我们调用animate
方法时,它会执行一些动画操作,并在动画完成后调用completionHandler
进行处理。
使用陷阱
避免循环引用
当逃逸闭包捕获了self
或其它对象时,可能会发生循环引用的问题。如果不处理这个问题,会导致内存泄漏和应用程序的崩溃。为了避免循环引用,我们需要使用一个弱引用或无主引用来捕获self。例如:
class MyClass {
var completionHandler: (() -> Void)?
func doSomething() {
someAsyncOperation { [weak self] in
self?.completionHandler?()
}
}
}
在这个例子中,我们使用了一个弱引用self,避免了循环引用问题。当逃逸闭包被调用时,我们使用self?.completionHandler?()
来访问completionHandler
属性,因为此时self
可能已经被释放了
总结
在Swift
中,@escaping
关键字用于定义逃逸闭包,逃逸闭包通常作为异步API的回调参数使用。使用逃逸闭包有许多好处
- 异步API的回调参数必须是逃逸闭包。由于异步API执行时间较长,非逃逸闭包可能会在函数返回之前就被销毁。因此,逃逸闭包可以确保在异步操作完成后一定会执行
- 在某些情况下,闭包需要在函数外部被调用。例如,如果我们要将一个闭包作为一个数组的元素,那么闭包必须是逃逸闭包
- 逃逸闭包可以使代码更加简洁和易于管理。当一个函数需要异步执行时,我们不需要创建一个新的类或对象来管理异步操作的结果,而是可以使用逃逸闭包来处理它们