这里每天分享一个 iOS 的新知识,快来关注我吧
![](https://file.jishuzhan.net/article/1772902960183906306/9946bf9f660735c7a1c9bf19c1f49761.webp)
前言
在 Swift 中,defer
语句提供了一种便捷的方式来编写在当前作用域退出时执行的代码。这对于资源管理非常有用,比如关闭文件句柄、释放手动分配的内存等。今天来探讨一下 defer
的用法,并结合 Swift 官方源码来解析其实现原理。
defer 的用法
defer
语句让你能够在即将离开当前作用域时执行一些清理工作。不论是由于 return
语句提前退出,还是抛出了一个错误,亦或是简单地到达作用域的末尾,defer
块中的代码都将被执行。
下边是一些具体的使用场景:
1. 锁的获取与释放
在多线程编程中,锁是一种常见的同步机制。使用 defer
来释放锁可以保证即使在发生错误或提前返回的情况下,锁也能被正确释放。
csharp
func safeMethod() {
lock.lock()
defer {
lock.unlock()
}
// 执行需要同步的代码
// 如果这里有 return 或抛出异常,defer 仍然保证锁被释放
}
2. 数据库操作
在进行数据库操作时,通常需要在操作结束后关闭数据库连接或事务。使用 defer
可以确保这些清理工作在各种退出路径上都能被执行。
go
func updateDatabase() {
let connection = database.connect()
defer {
connection.close()
}
// 执行数据库更新操作
// 不管操作成功还是失败,连接都会被关闭
}
3. 文件处理
在处理文件时,确保文件在不再需要时被关闭是很重要的。defer
语句可以帮助我们达到这个目的。
swift
func processFile(path: String) throws {
let file = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
defer {
file.closeFile()
}
// 读取并处理文件内容
}
实现原理
要深入理解 defer
的工作原理,我们需要查看 Swift 的源码。在 Swift 项目的 GitHub 仓库中,有一个文件 Defer.h
,其中定义了 defer
相关的实现细节。
arduino
#ifndef SWIFT_BASIC_DEFER_H
#define SWIFT_BASIC_DEFER_H
#include "llvm/ADT/ScopeExit.h"
namespace swift {
namespace detail {
struct DeferTask {};
template<typename F>
auto operator+(DeferTask, F &&fn) ->
decltype(llvm::make_scope_exit(std::forward<F>(fn))) {
return llvm::make_scope_exit(std::forward<F>(fn));
}
}
}
#define DEFER_CONCAT_IMPL(x, y) x##y
#define DEFER_MACRO_CONCAT(x, y) DEFER_CONCAT_IMPL(x, y)
#define SWIFT_DEFER \
auto DEFER_MACRO_CONCAT(defer_func, __COUNTER__) = \
::swift::detail::DeferTask() + [&]()
#endif // SWIFT_BASIC_DEFER_H
这段代码是用 C++ 写的,但它揭示了 defer
的核心机制。简单解释一下上边的代码:
-
SWIFT_DEFER
宏首先通过DEFER_MACRO_CONCAT
宏和__COUNTER__
宏生成一个唯一的变量名 -
__COUNTER__
是编译器提供的计数器,保证每个SWIFT_DEFER
实例的变量名都是唯一的 -
然后,它创建了一个
swift::detail::DeferTask
的实例,并使用+操作符重载将其与一个lambda
表达式相加。这个lambda
表达式是在SWIFT_DEFER
宏后面的代码块中定义的,它是在作用域结束时需要执行的代码。 -
通过+操作符重载,返回了一个由
llvm::make_scope_exit
创建的对象,该对象会在其析构函数中执行lambda
表达式。 -
这个返回的对象被赋值给了一个局部变量,当这个局部变量的作用域结束时(即离开
SWIFT_DEFER
所在的作用域时),它的析构函数会自动被调用,从而执行之前定义的lambda
表达式。 -
每个
SWIFT_DEFER
实例化的对象都遵循 C++ 局部变量的生命周期规则,最后声明的局部变量会首先被销毁(LIFO 后进先出)。
了解了以上实现原理,我们来看一些特殊场景:
1. 一个作用域中有多个 defer 时
上边说了局部变量的销毁是 LIFO 后进先出的,所以当有多个 defer
时,最后一个会先执行:
go
func complexOperation() {
defer { print("清理操作 1") }
defer { print("清理操作 2") }
print("执行某些操作")
}
// 输出:
// 执行某些操作
// 清理操作 2
// 清理操作 1
2. 在 for 循环中使用
在循环中使用 defer
时,每次迭代都会创建一个新的 defer
块。这意味着 defer
中的代码会在每次迭代结束时执行,而不是在整个循环结束后。
swift
for item in collection {
defer { print("处理 \(item)") }
if item.shouldSkip {
continue
}
// 处理项
}
在这个例子中,即使 continue
语句跳过了某些迭代,每个 defer
也都会在其对应的迭代结束时执行。
3. 在 defer 中可以用 return 吗?
答案是不能 ,如果在 defer
中使用 return
语句,编译器会直接报错 'return' cannot transfer control out of a defer statement
,这是因为 defer
中的代码是在作用域结束后执行的,也就是说,执行到 defer
的时候,函数已经 return
过了。
4. 在一个作用域下只写一个 defer 可以吗?
答案是可以,但没必要 ,因为如果作用域下没有其他代码,defer
下的代码会立即执行,因此 defer
也就失去了意义,硬要这么写的话编译器会给个警告,但不会报错:
go
func doSoming() {
defer {
print("doSoming")
}
}
结论
defer
是 Swift 中一个非常有用的特性,它简化了资源管理和清理工作的代码编写。
在实际开发中,合理利用 defer
不仅可以减少错误,还能使代码更加清晰和易于维护。除此之外 defer
还是一个经常在面试中被问到的知识点,希望通过本篇文章能帮助你更好地理解和使用 defer
语句。
这里每天分享一个 iOS 的新知识,快来关注我吧
本文同步自微信公众号 "iOS新知",每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!