当前位置: 首页 > news >正文

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捕获了runningTotalamount两个变量。每次调用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 (Result<User, 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 的数组提供了mapfilterreduce等高阶函数,它们都接收闭包作为参数,是闭包最广泛的应用场景之一。

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) // 220

11. 总结

闭包是 Swift 中非常强大且灵活的特性,掌握闭包是写出简洁、优雅 Swift 代码的关键。本文从闭包的基础语法出发,逐步介绍了语法简化、尾随闭包、值捕获、逃逸闭包和自动闭包等核心概念,并通过实战案例展示了闭包在异步回调和函数式编程中的典型应用。

在实际开发中,建议多使用闭包的简化写法来提升代码可读性,同时注意逃逸闭包中的循环引用问题,合理使用[weak self]来避免内存泄漏。

http://www.jsqmd.com/news/1407437/

相关文章:

  • 企业间货物买卖合同出现买方拖欠货款,专业买卖合同纠纷律所如何固定履约证据链 - 好物分享知识传播
  • eNSP设备启动失败全攻略:从VirtualBox兼容性到错误代码深度解析
  • 从Docker到nerdctl:容器CLI工具演进与K8s环境实战指南
  • YOLO目标检测中LoRA模块插入位置策略与实战指南
  • Modbus协议深度解析:从核心原理到工业通信实战避坑指南
  • 【AVDTP】规范精讲[8-4]: 流状态机全生命周期控制:从就绪启动到暂停关闭全拆解
  • 智能体记忆错误修复:依赖引导回滚机制的设计与实现
  • 揭秘磁盘存储:从物理结构到文件系统
  • ONNX模型部署实战:从导出报错到Android端量化部署全解析
  • HTTP断点续传实战:从原理到分块上传与状态管理的完整实现
  • 涉及股权、虚拟财产的多类型财产继承纠纷,专业财产继承律所如何梳理遗产范围 - 好物分享知识传播
  • LLM as Judge与Best of N:构建自优化AI代码生成流水线
  • c语言的常见概念和数据类型及变量
  • 异步任务状态机设计:解决图片生成任务丢失与系统可靠性问题
  • 2026年08月移动式防爆吸尘器品牌评测推荐:三个品牌大比拼,哪个更好? - 工业清洁测评社
  • 跨市场量化实战:使用 QuantDash 快速调取沪深 300 / 标普 500 / 恒生指数作为策略基准
  • MCP协议:AI Agent的TCP/IP时刻,从单机智能到网络智能
  • 学 Simulink—— 三相 PWM 整流器开路故障下的容错控制仿真
  • 手把手教你学 Simulink—— 半导体光刻机工件台永磁直线电机的无模型自适应控制仿真
  • 第 5 章 SVPWM 空间矢量调制:FOC 的最后一块拼图
  • 缓冲区溢出漏洞原理、利用与防御全解析:从栈溢出到ROP攻击
  • 基于 QuantDash 5 分钟 K 线的网格交易策略参数网格搜索寻优实战
  • 2026年8月行业内发泡管供应商推荐,海绵管/PE发泡管/地暖保温管/PP发泡管/泡沫棒,发泡管厂商口碑推荐 - 企业权威推荐大使
  • 涉及再婚家庭的多类型财产继承分割,专业律所如何平衡继子女与婚生子女继承权益 - 好物分享知识传播
  • GPT-5.6 Sol、Terra、Luna 怎么选?3 类任务决策表
  • Steve Brunton | Probability Bootcamp | 笔记 | 第四部分:高级统计 | Lecture 36 | 中心极限定理
  • Python 量化实战:如何高效抓取并分类 A 股主板、创业板、科创板与北交所实时行情
  • 某里RAG三面追问:知识库检索不到怎么办?四层兜底架构与工程边界
  • CentOS服务器性能排查与健康检查:从硬件到进程的完整诊断指南
  • Draw.io 高阶技巧:从绘图工具到架构设计与团队协作的生产力引擎