Swift 中 unowned self 的隐晦陷阱:为什么“无主引用”可能毁掉你的 App
若你只想记住一句话:“当闭包生命周期可能长于 self 时,永远不要使用 unowned。” 从一段崩溃代码说起 运行步骤: 用户进入页面 → Timer 持有闭包 → 闭包持有 unowned se
QoS 和线程优先级并不是 1:1 映射,它们只是相关,但机制上存在差别。下面详细解释:
QoS 是 GCD 的任务级别优先策略,用于指导系统调度任务和线程资源;线程优先级(pthread / sched)是操作系统层面线程调度权重。二者关联,但并非严格一一对应。
换句话说:
当你提交一个任务到队列:
GCD 查看任务 QoS
选择或创建合适线程:
线程可能共享:
⚠️ 所以一个线程上可能交替执行不同 QoS 的任务,线程优先级会动态调整,并非固定 1:1
macOS/iOS 内核会综合考虑:
因此:
| 特性 | QoS (GCD) | 线程优先级 (pthread / sched) |
|---|---|---|
| 粒度 | 任务级别 | 线程级别 |
| 作用 | 指导系统调度任务和线程分配 | 决定线程在 CPU 上调度权重 |
| 线程绑定 | 任务可能在任何线程上执行 | 固定线程调度权重 |
| 动态性 | GCD 可动态提升/继承 QoS | 通常固定,除非手动修改 |
| 对能耗影响 | 高 QoS → 可能更激进分配 CPU | 无直接能耗优化机制 |
GCD 文档说明:
.userInteractive / .userInitiated → 高 QoS → 系统会尽量在高优先级线程或更多线程执行.utility / .background → 低 QoS → 系统可能延迟执行或降低线程 CPU 优先级✅ 核心:高 QoS 更可能获得高线程优先级,但不是一条任务对应一条线程的固定关系
QoS 影响 GCD 如何选择线程和调度任务,但不是 1:1 映射到线程优先级;线程优先级只是系统层面的调度权重,GCD 会动态调整线程以匹配任务 QoS。
resume()
let source = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
source.schedule(deadline: .now(), repeating: 1.0)
source.setEventHandler { print("Timer fired") }
// source.resume() // ⚠️ 忘记 resume
resume() 才会启动事件源resume();如果需要暂停/恢复,结合 suspend() 使用DispatchSource.makeTimerSource(queue: .global()).schedule(deadline: .now(), repeating: 1.0).setEventHandler {
print("Fired")
}
class MyClass {
var timer: DispatchSourceTimer?
func start() {
timer = DispatchSource.makeTimerSource(queue: .global())
timer?.schedule(deadline: .now(), repeating: 1.0)
timer?.setEventHandler { print("Fired") }
timer?.resume()
}
}
坑:认为事件处理 block 内访问的资源线程安全
原因:DispatchSource 的回调在指定队列上执行,并发队列上可能同时执行多个事件 block
后果:共享资源竞争、数据不一致
解决:
source.resume()
source.resume() // ⚠️ 再次 resume 会 crash
坑:DispatchSource 只能 resume 一次
原因:resume 用来启动事件源,重复调用会触发异常
解决:
let queue = DispatchQueue(label: "serial")
let source = DispatchSource.makeTimerSource(queue: queue)
source.setEventHandler {
queue.sync { print("Deadlock") } // ⚠️ 死锁
}
source.resume()
source.cancel()
source.setEventHandler(nil) // 释放闭包引用
resume/suspend
坑:误认为 suspend/resume 可以无限暂停定时器
注意:
坑:将 DispatchSource 绑定到主队列或串行队列,但事件量大
后果:
解决:
| 坑 | 解决方案 |
|---|---|
| 忘记 resume | 必须调用 resume() 启动事件源 |
| DispatchSource 被释放 | 保持强引用(类属性) |
| 线程安全误区 | 并发队列访问共享资源加锁,或使用串行队列 |
| 重复 resume 导致 crash | 只 resume 一次,使用标志位 |
| sync 导致死锁 | 避免在事件 block 内对同队列 sync,使用 async |
| 忘记 cancel | 使用完毕后 cancel,并释放事件处理器闭包 |
| suspend/resume 不成对 | 严格成对调用,确保队列状态正确 |
| 队列选择不当 | 高频事件用后台并发队列,UI 事件用主队列 |
💡 核心经验: