iOS Swift 线程开发指南
1、线程介绍Thread
线程是操作系统进行任务调度的基本执行单元,负责承载应用程序中各类任务的运行。
iOS 应用主要涉及两种线程:主线程(Main Thread) 和子线程,它们各司其职,共同保障应用的流畅与稳定。
1.1、主线程(Main Thread)
主线程是APP启动时由系统自动创建的第一个线程,也称为 UI 线程。
主要特点:
1、App 启动时自动创建(无需开发者创建)
2、唯一的 UI 线程
3、驱动整个应用运行
主要职责:
1、所有和界面相关的操作:UI渲染、更新、展示…
2、所有和用户交互的操作:点击、滑动、手势识别…
注意:禁止在主线程执行任何耗时操作,会影响用户体验。
原因:主线程运行在一个叫做 RunLoop 的循环中,这个循环负责:
1、处理用户交互(触摸、点击等)
2、更新 UI 界面
3、执行系统事件
如果主线程执行了耗时操作,RunLoop 就会被阻塞,导致上述所有操作都无法及时处理。
从而会导致:
1、屏幕刷新率下降(低于 60Hz)
2、动画变得卡顿或不流畅
3、触摸事件无法及时响应
4、应用看起来像"死机"了一样
更严重的是,iOS 系统还有一个 watchdog 计时器(看门狗机制):
watchdog 主要监控场景和超时时间(不同系统版本中,时间规定会略有差异):
1、应用启动必须在限定时间内完成(应用启动必须在限定时间内完成)
冷启动:20 秒
热启动:10-15 秒
.
2、主线程被阻塞的最长时间(主线程被阻塞的最长时间)
普通操作:5-10 秒
特殊场景:可能更短
.
3、后台任务超时(应用进入后台后的执行时间)
iOS 12 及之前:约 5 分钟
iOS 13 及之后:约 30 秒
如果主线程无响应5-10 秒,应用会被强制终止或者系统弹出警告:“App无响应”。 此时在在 Xcode 控制台可能看到日志:
Exception Type: EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d
注意:0x8badf00d 这个错误代码就是 "ate bad food" 的谐音,专门表示 Watchdog 超时
1.2、子线程
子线程是用来辅助主线程的,专门帮主线程处理“它不擅长”的任务,让主线程UI处理得更高效。
主要特点:
1、子线程是由开发者主动创建的,与主线程共享应用内存空间。
2、子线程数量,理论上无硬性上限,但是受设备内存和 CPU 制约,数量不宜过多,通常活跃线程数在 [CPU核心数 × (1~2)] 个为宜。
主要职责,处理耗时工作:
1、网络请求:所有 API 调用、数据下载。
2、文件 I/O:大文件读写、数据库操作。
3、数据处理:图片解码/处理、视频压缩、复杂计算。
4、其他耗时操作:任何可能导致主线程卡顿的任务。
1.3、iOS 线程的精髓
我们开发时,核心目标是“让主线程保持 60FPS 的流畅渲染,给用户最丝滑的体验”。大家主要记住这三条,就掌握了 iOS 线程的精髓
1、主线程 = 展示层 - 只负责 UI 显示和用户交互
2、子线程 = 工作层 - 处理所有可能耗时的任务
3、线程间通信 - 子线程数据处理完成后,可通过 @MainActor 或 DispatchQueue.main.async 等桥接机制,将结果安全传递至主线程进行 UI 更新。
不仅iOS客户端,安卓、鸿蒙客户端其实也是类似的(虽然鸿蒙子线程设计的不够现代化,但思想上是一致的)。
不知道大家是否认同呢?欢迎在评论区分享您的见解哈。
2、 Swift 中子线程并发方案选择
Swift中实现子线程并发的可选方案如下:
1、Swift 并发 (Task) - ⭐️⭐️⭐️⭐️⭐️ 首选
2、GCD (Grand Central Dispatch) - ⭐️⭐️⭐️⭐️ 备选
3、OperationQueue - ⭐️⭐️⭐️ 特定场景
4、Thread - ⭐️⭐️ 不推荐
5、其他:pthread、NSThread、 std::thread等OC的实现 - ⭐️ 不考虑
2.1、Swift 并发 (Task)
Swift Task 是 Apple 推出的现代化、结构化并发解决方案,它通过 async/await 语法和 Actor 模型,在编译器层面保证了并发代码的安全性和可维护性,彻底改变了 iOS/macOS 平台的异步编程范式。
思维转变:
1、传统线程思维:“我要创建线程,管理线程,同步线程” (关注的是执行机制)
2、Swift Task 思维:“我要完成什么工作,需要什么数据”(关注的是业务目标)
错误认知:Task 会取代线程
正确认知:Task 在线程之上提供了更高级的抽象
Task与线程关系比喻:
线程 = 工厂的机器设备(物理资源)
Task = 生产订单(逻辑任务)
系统 = 智能调度员(自动分配订单到合适设备)
一个线程可以执行多个 Task,一个 Task 也可能在多个线程中执行(挂起和恢复时)。
相较于传统的线程管理,Task解决了哪些问题?
1、资源浪费问题:
线程是重量级资源,创建慢且占用大量内存,等待时还会阻塞浪费CPU。
而Task内存占用极轻,能在少量线程上切换运行,等待时主动挂起释放资源,实现CPU与内存的高效利用。
.
2、"回调地狱"问题
传统回调导致代码嵌套层层加深,形成难以维护的“回调地狱”,错误处理也分散各处。
Task 通过 async/await 语法,将异步代码转为线性书写,使逻辑清晰、错误处理集中,彻底摆脱回调嵌套。
.
3、数据竞争问题
传统线程需手动加锁防数据竞争,问题难发现、难调试。
Task 的 Actor 模型由编译器自动保护共享数据,从根源杜绝竞争。
.
4、生命周期管理问题
传统线程需手动管理,易泄漏且取消困难。
Task 结构化并发支持自动级联取消,资源管理自动化。
.
5、优先级和调度问题
传统线程手动管理优先级,易出调度问题。
Task 系统自动调度,智能处理优先级与负载,开发者只需标注重要性。
Swift Task线程自动管理:
Swift 运行时为每个应用进程维护了一个全局线程池。当我们创建 Task 时,任务会被智能调度器自动分配到线程池中的空闲线程上执行。任务完成后,线程立即释放回池中复用,从而高效管理并发并避免线程创建销毁的开销。
2.2、GCD (Grand Central Dispatch)
GCD (Grand Central Dispatch) 是苹果开发的底层并发框架,基于线程池模型,通过队列管理任务执行。
核心特点:
1、队列管理 - 提供了串行队列、并发队列、主队列
2、自动线程管理 - 系统全局线程池,自动复用线程(与Swift Task线程池非同一个)
3、优先级系统 - 不同QoS级别管理任务优先级
4、C语言基础 - 轻量级、接近系统底层
GCD 是基于线程池的底层并发框架,通过队列管理、操作线程,适合简单的后台任务和UI调度。对于复杂异步流程和新项目,Swift Task 在性能、安全性和开发体验上全面优于 GCD,GCD仅建议用于维护旧代码(兼容老旧代码库、或者C/Objective-C项目)或简单场景。
2.3、OperationQueue
OperationQueue 是基于 GCD 的高级抽象,它在 GCD 之上添加了面向对象的任务管理能力。
相比 GCD 的优势:
1、任务依赖:可明确设置操作间的执行顺序依赖关系
2、状态管理:可查询操作状态(准备中、执行中、完成、取消)
3、精确控制:可取消单个操作或挂起整个队列
4、最大并发数:精确控制同时执行的操作数量
5、Completion Block:每个操作支持完成回调
OperationQueue相比Swift Task存在明显局限:API冗长,需创建特定对象封装任务;存在额外性能开销;语法陈旧,无法使用现代async/await简洁语法;仍需手动处理内存管理问题。这些不足使其在现代Swift并发编程中竞争力不足。
2.4、Thread线程
Thread 是 iOS/macOS 平台最基础的并发实现,它直接封装了底层的 POSIX 线程,为开发者提供了面向对象的线程管理接口。
Thread作为最底层线程方案,由开发者自行管理维护线程对象。存在资源消耗大、创建销毁成本高、上下文切换昂贵、同步调试困难、缺乏任务抽象和取消机制等核心问题。
在现代并发编程中已被更高效的GCD和Swift Task全面取代。
2.5、Swift并发方案建议
✅ 在 Swift 项目中,应首选使用 Swift 并发处理所有异步任务和 UI 更新。
⚠️ GCD 仅用于与传统 API 交互或特定性能优化场景。
⚠️ OperationQueue 适用于需要精细控制任务依赖关系的复杂工作流。
⚠️ Thread 因抽象层级过低,除系统级底层开发外应彻底弃用。
3、Swift 并发功能介绍
3.1、Swift 并发模型的核心功能
1、async/await
异步函数:用 async 标记,表示函数内部可以“暂停”等待。
等待结果:用 await 调用异步函数,此时当前任务会挂起,不阻塞线程,直到结果就绪。
.
2、Task
工作单元:代表一项可以在后台执行的异步任务。
入口点:它是从同步世界进入异步世界的起点。
.
3、结构化并发
父子关系:任务可以拥有子任务,形成清晰的层级结构。
自动管理:父任务会自动等待所有子任务完成,并且取消操作会自动传递给所有子任务,防止任务“失控”。
.
4、Actor
数据保镖:一种引用类型,专门用于在并发环境中安全地管理共享数据。
串行访问:它确保在任何时刻,只有一个任务能访问其内部数据,从而从根本上防止数据竞争。
.
5、@MainActor
主线程调度员:一个特殊的Actor,它确保所有标记给它的代码(尤其是UI更新)都在主线程上安全执行。
3.2、主线程中执行任务
在主线程中使用 Task 的主要意义是:
1、在异步上下文中安全地更新 UI
2、确保某些操作必须在主线程执行
3、避免线程竞争条件
在同步方法中调用异步方法 - 必须用 Task:
// 同步方法中调用异步方法必须用 Task
func syncFunction() {
// ❌ 错误:不能在同步函数中直接调用异步函数
// await fetchData()
// ✅ 正确:必须用 Task 包装
Task {
await fetchData()
}
}
在 SwiftUI 中调用异步方法:
struct ContentView: View {
var body: some View {
Button("加载数据") {
// ✅ SwiftUI 的 action 闭包中需要 Task
Task {
await loadData()
}
}
}
// .task 修饰符会自动处理
var body2: some View {
List(items, id: \.id) { item in
Text(item.name)
}
.task {
// ✅ 不需要显式 Task,框架自动处理
await loadData()
}
}
}
多个 Task 的执行特点:
// 示例:创建多个独立的 Task
Task { @MainActor in
print("Task 1 开始 - 线程:", Thread.current)
self.label.text = "任务1"
}
Task { @MainActor in
print("Task 2 开始 - 线程:", Thread.current)
self.button.isEnabled = false
}
Task { @MainActor in
print("Task 3 开始 - 线程:", Thread.current)
self.indicator.stopAnimating()
}
// 执行结果:
// Task 1 开始 - 线程: <NSThread: 0x...>{number = 1, name = main}
// Task 2 开始 - 线程: <NSThread: 0x...>{number = 1, name = main}
// Task 3 开始 - 线程: <NSThread: 0x...>{number = 1, name = main}
// 所有 Task 都在主线程执行,但执行顺序不确定!
避免阻塞主线程案例:
class ResponsiveUIExample {
// ❌ 阻塞主线程的做法
func blockingOperation() {
// 如果在主线程执行耗时操作会卡住 UI
performHeavyCalculation() // 如果这个函数是同步的,会阻塞主线程
}
// ✅ 使用 Task 保持 UI 响应性
func nonBlockingOperation() {
Task {
// 1. 立即响应用户操作
await showProgressFeedback()
// 2. 在后台执行耗时操作
let result = await performHeavyCalculationAsync()
// 3. 回到主线程显示结果
await displayResult(result)
}
// UI 保持响应,用户可以继续交互
}
@MainActor
private func showProgressFeedback() async {
// 显示加载状态
}
private func performHeavyCalculationAsync() async -> String {
// 在后台线程执行繁重计算
return await Task.detached {
print("繁重计算在线程: \(Thread.current)")
// 模拟复杂计算
Thread.sleep(forTimeInterval: 3)
return "计算结果"
}.value
}
}
3.3、子线程中执行任务
class BackgroundExample {
func runInBackground() {
Task.detached {
//detached 切换到后台子线程执行任务
print("后台线程: \(Thread.current)")
await self.heavyWork()
}
}
private func heavyWork() async {
print("繁重任务执行中")
}
}
3.4、子线程切换主线程方式
有两种主流切换方式:
1、MainActor.run:最直接的线程切换方式
2、@MainActor:自动确保函数在主线程执行
使用 Task 和 @MainActor:
// 在后台线程执行,然后切换回主线程
Task {
// 在后台线程执行耗时操作
let result = await someHeavyWork()
// 自动切换回主线程更新 UI
await MainActor.run {
label.text = result
// 这里已经在主线程了
}
}
使用 @MainActor 属性包装器:
@MainActor
func updateUI() {
// 这个函数会自动在主线程执行
label.text = "Hello"
button.isEnabled = true
}
Task {
let data = await fetchData()
await updateUI() // 自动切换到主线程
}
3.5、并发执行任务
Task {
// 使用 async let 并发执行多个异步操作
async let user = fetchUser() // 启动用户数据获取,不等待
async let posts = fetchPosts() // 启动文章数据获取,不等待
// 等待两个任务都完成
let (userData, postData) = await (user, posts)
print("并发完成: \(userData), \(postData.count)篇文章")
}
func fetchUser() async -> String {
try? await Task.sleep(nanoseconds: 2_000_000_000) // 模拟2秒请求
return "用户信息"
}
func fetchPosts() async -> [String] {
try? await Task.sleep(nanoseconds: 1_000_000_000) // 模拟1秒请求
return ["文章1", "文章2"]
}
3.6、任务组并发
Task {
let total = await withTaskGroup(of: Int.self) { group in
// 添加3个并发任务
for i in 1...3 {
group.addTask {
return await self.processItem(i) // 每个任务处理一个项目
}
}
var sum = 0
for await result in group { // 按完成顺序收集结果
sum += result // 累加每个任务的结果
}
return sum
}
print("任务组总和: \(total)")
}
func processItem(_ id: Int) async -> Int {
try? await Task.sleep(nanoseconds: 500_000_000) // 模拟处理时间
return id * 10 // 返回处理后的值
}
3.7、顺序执行
Task {
// 异步代码必须在 Task 中执行
let step1 = await downloadData() // 等待第一步完成
let step2 = await processData(step1) // 用第一步结果执行第二步
let step3 = await saveData(step2) // 用第二步结果执行第三步
print("顺序执行完成: \(step3)")
}
func downloadData() async -> String {
"原始数据" // 模拟下载
}
func processData(_ data: String) async -> String {
"处理后的: \(data)" // 模拟数据处理
}
func saveData(_ data: String) async -> String {
"已保存: \(data)" // 模拟数据保存
}
3.8、任务取消
let task = Task {
for i in 1...5 {
try Task.checkCancellation() // 检查任务是否被取消
print("任务进度: \(i)")
try await Task.sleep(nanoseconds: 1_000_000_000) // 等待1秒
}
return "任务完成"
}
// 2秒后取消任务
Task {
try await Task.sleep(nanoseconds: 2_000_000_000)
task.cancel() // 取消任务
print("任务已取消")
}
3.9、线程安全 Actor
actor SafeCounter {
private var count = 0 // 私有状态,受到actor保护
func increment() {
count += 1 // 安全修改,不会数据竞争
}
func getValue() -> Int {
return count // 安全读取
}
}
let counter = SafeCounter()
Task {
await withTaskGroup(of: Void.self) { group in
for _ in 1...100 {
group.addTask {
await counter.increment() // 安全并发访问
}
}
}
let final = await counter.getValue()
print("最终计数: \(final)") // 保证是100
}
3.10、优先级控制
let highPriorityTask = Task(priority: .high) {
print("高优先级任务执行")
}
let lowPriorityTask = Task(priority: .low) {
print("低优先级任务执行")
}
// 系统会优先调度高优先级任务
82

被折叠的 条评论
为什么被折叠?



