Swift 闭包:从基础语法到实战进阶

发布时间:2026/8/16 22:49:10
Swift 闭包:从基础语法到实战进阶 1. 什么是闭包闭包是 Swift 中一种自包含的函数代码块它可以捕获并存储其所在上下文中的常量和变量。简单来说闭包就是可以像普通函数一样被调用、可以赋值给变量、可以作为参数传递的代码块。在 Swift 中全局函数和嵌套函数本质上也是闭包的一种特殊形式。闭包有三种形式全局函数有名字但不会捕获任何值的闭包。嵌套函数有名字且可以捕获其所在函数体内值的闭包。闭包表达式轻量级语法可以捕获其所在上下文中值的匿名闭包。2. 闭包基础语法闭包表达式的基本语法格式如下{ (参数列表) - 返回类型 in 函数体代码 }下面通过一个简单的排序示例来理解闭包的基本用法。Swift 标准库中的sorted(by:)方法接收一个闭包作为参数用于定义排序规则let names [Chris, Alex, Ewa, Barry, Daniella] // 使用普通函数作为排序规则 func backward(_ s1: String, _ s2: String) - Bool { return s1 s2 } var reversedNames names.sorted(by: backward) // 使用闭包表达式 reversedNames names.sorted(by: { (s1: String, s2: String) - Bool in return s1 s2 })上面的闭包表达式完整地写出了参数类型和返回类型但 Swift 提供了多种简化写法让代码更加简洁。3. 闭包语法简化3.1 根据上下文推断类型因为sorted(by:)方法的参数类型是已知的Swift 可以推断出闭包的参数类型和返回类型因此可以省略类型声明reversedNames names.sorted(by: { s1, s2 in return s1 s2 })3.2 单表达式隐式返回如果闭包体只有一行表达式可以省略return关键字reversedNames names.sorted(by: { s1, s2 in s1 s2 })3.3 简写参数名Swift 自动为闭包提供简写参数名$0、$1、$2等可以省略参数列表和in关键字reversedNames names.sorted(by: { $0 $1 })3.4 运算符方法对于字符串比较这种场景可以直接传入运算符函数因为本身就是一个接收两个参数并返回 Bool 的函数reversedNames names.sorted(by: )4. 尾随闭包当闭包是函数的最后一个参数时可以将闭包写在函数调用的括号之外这种写法称为尾随闭包。它让代码在函数较长时更加易读// 不使用尾随闭包 reversedNames names.sorted(by: { $0 $1 }) // 使用尾随闭包 reversedNames names.sorted() { $0 $1 } // 如果闭包是唯一参数可以省略函数调用的括号 reversedNames names.sorted { $0 $1 }尾随闭包在 SwiftUI 中非常常见例如按钮的点击事件处理Button(点击我) { print(按钮被点击了) }5. 捕获值闭包可以在其函数体内捕获并存储所在上下文中定义的常量和变量。即使这些常量和变量的原始作用域已经不存在闭包仍然可以在其函数体内引用和修改它们。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()) // 输出 10 print(incrementByTen()) // 输出 20 print(incrementByTen()) // 输出 30上面的示例中嵌套函数incrementer捕获了runningTotal和amount两个变量。每次调用incrementByTen时runningTotal都会在上一次的基础上继续累加。需要注意的是如果闭包捕获的是变量它可以在闭包内部修改该变量的值如果捕获的是常量则只能读取不能修改。6. 闭包是引用类型在 Swift 中函数和闭包都是引用类型。当你把一个闭包赋值给另一个变量时实际上是将同一个闭包的引用赋值过去两个变量指向同一个闭包let incrementByTen makeIncrementer(forIncrement: 10) let alsoIncrementByTen incrementByTen print(incrementByTen()) // 输出 10 print(alsoIncrementByTen()) // 输出 20两个变量共享同一个闭包7. 逃逸闭包与非逃逸闭包7.1 非逃逸闭包默认情况下闭包是非逃逸的。非逃逸闭包在函数返回之前就会被调用不会在函数返回后继续存在。由于不需要考虑生命周期问题非逃逸闭包在使用时更加高效func performOperation(_ operation: () - Void) { // 闭包在函数体内被调用 operation() } performOperation { print(这是一个非逃逸闭包) }7.2 逃逸闭包当闭包需要在函数返回之后才被调用时需要在参数类型前加上escaping关键字将其标记为逃逸闭包。最常见的场景是异步网络请求var completionHandlers: [() - Void] [] func downloadData(completion: escaping () - Void) { // 将闭包存储到数组中函数返回后闭包仍然存在 completionHandlers.append(completion) // 模拟异步操作 DispatchQueue.global().async { // 模拟网络请求耗时 sleep(2) // 在异步任务完成后调用闭包 completion() } } downloadData { print(数据下载完成) }逃逸闭包需要注意循环引用问题。如果闭包内部捕获了self而self又持有该闭包就会形成循环引用导致内存泄漏。此时需要使用[weak self]或[unowned self]来打破循环class DataLoader { var data: String func loadData() { downloadData { [weak self] in guard let self self else { return } self.data 加载完成 print(self.data) } } }8. 自动闭包自动闭包是一种自动创建的闭包用于包装传递给函数作为参数的表达式。自动闭包不接受任何参数当它被调用时会返回包装在其中的表达式的值。使用autoclosure关键字来标记func logIfTrue(_ condition: autoclosure () - Bool) { if condition() { print(条件为真) } } // 调用时不需要写花括号直接传入表达式 logIfTrue(2 1) // 输出 条件为真自动闭包的一个典型应用是 Swift 中的assert函数。它只在调试模式下执行并且只有在条件为假时才计算表达式从而避免不必要的性能开销。9. 实战案例使用闭包实现回调下面通过一个完整的示例来展示闭包在真实项目中的应用。假设我们要实现一个简单的网络请求工具类import Foundation struct User { let id: Int let name: String } enum NetworkError: Error { case invalidResponse case serverError(code: Int) } class APIClient { // 使用逃逸闭包处理异步回调 func fetchUser(id: Int, completion: escaping (ResultUser, Error) - Void) { // 模拟网络请求 DispatchQueue.global().asyncAfter(deadline: .now() 1) { // 模拟请求成功 let user User(id: id, name: 用户\(id)) completion(.success(user)) // 模拟请求失败 // completion(.failure(NetworkError.serverError(code: 500))) } } } // 使用示例 let client APIClient() client.fetchUser(id: 1001) { result in switch result { case .success(let user): print(获取用户成功\(user.name)) case .failure(let error): print(获取用户失败\(error.localizedDescription)) } }10. 实战案例高阶函数与闭包Swift 的数组提供了map、filter、reduce等高阶函数它们都接收闭包作为参数是闭包最广泛的应用场景之一。let numbers [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // map对每个元素进行转换 let squaredNumbers numbers.map { $0 * $0 } print(squaredNumbers) // [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] // filter筛选符合条件的元素 let evenNumbers numbers.filter { $0 % 2 0 } print(evenNumbers) // [2, 4, 6, 8, 10] // reduce将所有元素合并为一个值 let sum numbers.reduce(0) { $0 $1 } print(sum) // 55 // 链式调用 let result numbers .filter { $0 % 2 0 } .map { $0 * $0 } .reduce(0, ) print(result) // 22011. 总结闭包是 Swift 中非常强大且灵活的特性掌握闭包是写出简洁、优雅 Swift 代码的关键。本文从闭包的基础语法出发逐步介绍了语法简化、尾随闭包、值捕获、逃逸闭包和自动闭包等核心概念并通过实战案例展示了闭包在异步回调和函数式编程中的典型应用。在实际开发中建议多使用闭包的简化写法来提升代码可读性同时注意逃逸闭包中的循环引用问题合理使用[weak self]来避免内存泄漏。