iOS Swift 线程开发指南

【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道! 10w+人浏览 1.4k人参与

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、线程间通信 - 子线程数据处理完成后,可通过 @MainActorDispatchQueue.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("低优先级任务执行")
}

// 系统会优先调度高优先级任务
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qixingchao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值