我们来详细对比一下 OC 和 Swift 中 Grand Central Dispatch (GCD) 的多线程用法,特别是线程保活等常见场景,并通过代码示例说明。
核心概念一致
首先,最重要的一点是:GCD 的核心概念、队列类型(串行、并发、主队列、全局队列)、任务调度方式(同步sync / 异步async)、组(dispatch_group_t)、信号量(dispatch_semaphore_t)、屏障(dispatch_barrier_async)等在 OC 和 Swift 中是相同的。底层都是同一个 C 库 (libdispatch)。差异主要体现在语法、API 命名风格以及 Swift 对闭包的现代支持上。
主要差异对比
| 特性/概念 | Objective-C (OC) | Swift | 说明 |
|---|---|---|---|
| 队列创建 |
dispatch_queue_create("com.example.queue", DISPATCH_QUEUE_SERIAL); dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
DispatchQueue(label: "com.example.queue", attributes: .serial) DispatchQueue.global(qos: .default)
|
Swift 使用 DispatchQueue 类,更面向对象。QoS 替代了旧的优先级常量。 |
| 主队列 | dispatch_get_main_queue() |
DispatchQueue.main |
Swift 访问主队列更简洁。 |
| 异步提交任务 | dispatch_async(queue, ^{ ... }); |
queue.async { ... } |
Swift 使用实例方法 .async 和尾随闭包,语法更清晰。 |
| 同步提交任务 | dispatch_sync(queue, ^{ ... }); |
queue.sync { ... } |
同上。 |
| 闭包语法 | Block: ^{ ... } 或 ^returnType (params) { ... }
|
Closure: { ... } 或 { (params) -> returnType in ... }
|
Swift 闭包语法更现代灵活,支持捕获列表 ([weak self])。 |
| Dispatch Group |
dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{...}); dispatch_group_notify(group, queue, ^{...}); dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
let group = DispatchGroup() queue.async(group: group) { ... } group.notify(queue: queue) { ... } group.wait()
|
Swift 使用 DispatchGroup 类,方法调用更面向对象。async(group:) 是主要提交方式。 |
| Dispatch Semaphore |
dispatch_semaphore_t sema = dispatch_semaphore_create(1); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); dispatch_semaphore_signal(sema);
|
let semaphore = DispatchSemaphore(value: 1) semaphore.wait() semaphore.signal()
|
Swift 使用 DispatchSemaphore 类,方法名更直观。 |
| Dispatch Barrier | dispatch_barrier_async(queue, ^{ ... }); |
queue.async(flags: .barrier) { ... } |
Swift 使用 .barrier 标志。 |
| 线程保活 (RunLoop) | 显式创建 NSThread + NSRunLoop + CFRunLoopRun()
|
显式创建 Thread + RunLoop + RunLoop.run() (或 CFRunLoop) |
核心逻辑相同:创建线程 -> 添加 Port/Source -> 启动 RunLoop。OC 常用 NSRunLoop,Swift 常用 RunLoop 或直接调用 CFRunLoopRun。 |
| QoS (Quality of Service) |
DISPATCH_QUEUE_PRIORITY_* (如 DISPATCH_QUEUE_PRIORITY_DEFAULT) |
.userInteractive, .userInitiated, .default, .utility, .background
|
Swift 的 QoS 枚举更语义化,推荐使用。 |
常用场景示例对比
场景 1: 后台异步任务 + 主线程更新 UI (最常见)
// Objective-C
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 在后台线程执行耗时操作 (e.g., 网络请求, 图片处理)
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
// 回到主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
// Swift
DispatchQueue.global(qos: .userInitiated).async {
// 在后台线程执行耗时操作
guard let imageData = try? Data(contentsOf: imageURL),
let image = UIImage(data: imageData) else { return }
// 回到主线程更新UI
DispatchQueue.main.async {
self.imageView.image = image // 注意: 这里通常需要 [weak self] 避免循环引用
}
}
关键点:
- OC:
dispatch_async+dispatch_get_global_queue/dispatch_get_main_queue+ Block。 - Swift:
.async方法 +DispatchQueue.global/DispatchQueue.main+ Closure。Swift 中强烈建议在闭包内使用self时使用捕获列表[weak self]或[unowned self]避免循环引用。
场景 2: 使用 Dispatch Group 管理多个异步任务完成
// Objective-C
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 任务1
dispatch_group_async(group, queue, ^{
[self downloadTask1];
});
// 任务2
dispatch_group_async(group, queue, ^{
[self downloadTask2];
});
// 所有任务完成后通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"All tasks completed!");
[self updateUI];
});
// Swift
let group = DispatchGroup()
let queue = DispatchQueue.global(qos: .userInitiated)
// 任务1
queue.async(group: group) {
self.downloadTask1()
}
// 任务2
queue.async(group: group) {
self.downloadTask2()
}
// 所有任务完成后通知
group.notify(queue: .main) {
print("All tasks completed!")
self.updateUI() // 注意循环引用
}
关键点:
- OC:
dispatch_group_create,dispatch_group_async,dispatch_group_notify。 - Swift:
DispatchGroup(),.async(group:)方法,group.notify(queue:)方法。逻辑完全一致。
场景 3: 线程保活 (RunLoop)
需求: 创建一个长期存活的后台线程,用于执行一系列需要在同一个线程上顺序处理的任务(例如:数据库操作、日志写入、特定串行网络请求等)。避免线程频繁创建销毁开销。
// Objective-C
// MyThreadManager.h
@property (nonatomic, strong) NSThread *workerThread;
// MyThreadManager.m
- (instancetype)init {
self = [super init];
if (self) {
_workerThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEntry) object:nil];
[_workerThread start];
}
return self;
}
- (void)threadEntry {
@autoreleasepool {
// 1. 给当前RunLoop添加一个Port/Source (保活关键)
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
// 或使用Source: CFRunLoopSourceContext sourceContext = {0}; ... CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 2. 启动RunLoop (RunLoop会一直运行直到所有Source/Timer被移除或显式停止)
[[NSRunLoop currentRunLoop] run];
// 更底层保活(常用): CFRunLoopRun();
}
}
// 向保活线程提交任务
- (void)performTaskOnWorkerThread:(void (^)(void))task {
if (!task) return;
[self performSelector:@selector(executeTask:) onThread:self.workerThread withObject:task waitUntilDone:NO];
}
- (void)executeTask:(void (^)(void))task {
task(); // 在workerThread上执行传入的block
}
// Swift (使用 Thread 和 RunLoop)
class ThreadManager {
private var workerThread: Thread?
init() {
workerThread = Thread(target: self, selector: #selector(threadEntry), object: nil)
workerThread?.start()
}
@objc private func threadEntry() {
autoreleasepool {
// 1. 给当前RunLoop添加一个Port (保活关键)
RunLoop.current.add(Port(), forMode: .default)
// 或使用 CFRunLoopSource
// 2. 启动RunLoop (RunLoop会一直运行直到所有Source/Timer被移除或显式停止)
RunLoop.current.run()
// 更底层保活(常用): CFRunLoopRun()
}
}
// 向保活线程提交任务 (使用 performSelector)
func performTaskOnWorkerThread(task: @escaping () -> Void) {
guard let workerThread = workerThread else { return }
perform(#selector(executeTask(_:)), on: workerThread, with: task, waitUntilDone: false)
}
@objc private func executeTask(_ task: () -> Void) {
task() // 在workerThread上执行传入的闭包 (注意: 这里闭包类型需要兼容 @objc)
}
// 向保活线程提交任务 (更Swift化的方式 - 需要自定义Source, 较复杂)
// ... (通常为了简便,performSelector 在保活线程场景仍常用)
}
// Swift (更现代的方式 - 使用 DispatchQueue + RunLoop/Source 混合)
class ThreadManager {
private let serialQueue = DispatchQueue(label: "com.example.workerThread")
private var runLoopSource: CFRunLoopSource?
init() {
serialQueue.async { [weak self] in
guard let self = self else { return }
autoreleasepool {
// 1. 创建自定义RunLoop Source上下文
var context = CFRunLoopSourceContext()
context.perform = { info in
// 这里处理被触发的任务 (需要配合队列存储任务)
// 例如: if let task = self.taskQueue.dequeue() { task() }
}
// 2. 创建Source并添加到当前RunLoop
let runLoop = CFRunLoopGetCurrent()
let source = CFRunLoopSourceCreate(nil, 0, &context)
CFRunLoopAddSource(runLoop, source, .defaultMode)
self.runLoopSource = source
// 3. 启动RunLoop
CFRunLoopRun()
}
}
}
func performTaskOnWorkerThread(task: @escaping () -> Void) {
// 1. 将任务放入一个线程安全的队列 (taskQueue)
// ... (使用 serialQueue.sync 或锁来操作 taskQueue)
// 2. 唤醒RunLoop处理任务
if let source = runLoopSource {
CFRunLoopSourceSignal(source)
CFRunLoopWakeUp(CFRunLoopGetCurrent()) // 注意: 需要在 serialQueue 上获取当前RunLoop
}
}
}
关键点 (线程保活):
- 核心原理: RunLoop 在没有 Source/Timer/Observer 时会退出。添加一个永久的 Source (如 Mach Port 或自定义 Source) 是保活的关键。
-
OC 常用模式:
NSThread+[[NSRunLoop currentRunLoop] addPort:...]+[[NSRunLoop currentRunLoop] run]或CFRunLoopRun()+performSelector:onThread:...提交任务。 -
Swift 常用模式:
-
类似 OC:
Thread+RunLoop.current.add(Port(), ...)+RunLoop.current.run()/CFRunLoopRun()+performSelector:onThread:...。注意闭包兼容@objc。 -
更 GCD/Swift 风格 (复杂): 在串行
DispatchQueue的async块中创建自定义CFRunLoopSource,将其添加到当前 RunLoop 并启动 RunLoop (CFRunLoopRun)。提交任务时,将任务放入队列并CFRunLoopSourceSignal+CFRunLoopWakeUp触发 Source 的处理函数来执行队列中的任务。这种方式更底层但更灵活,避免了performSelector。
-
类似 OC:
-
保活线程退出: 要停止保活线程,需要在目标线程上调用
CFRunLoopStop(CFRunLoopGetCurrent())并移除添加的 Source/Port。管理其生命周期很重要。 -
替代方案: 对于大多数需要“常驻”执行串行任务的场景,一个专用的串行
DispatchQueue(DispatchQueue(label: ..., attributes: .serial)) 通常是更简单、更推荐的做法。GCD 会高效地管理底层线程池。显式线程保活主要用于需要严格控制在 特定 线程执行任务(如某些 Core Data 上下文要求)、或需要 RunLoop 特定功能(如NSTimer在后台线程调度)的特殊情况。
总结
- 核心一致: OC 和 Swift 使用 GCD 的核心逻辑、队列类型、调度操作(async/sync/group/semaphore/barrier)是完全相同的。
-
语法差异:
-
OC: 基于 C 函数 (
dispatch_async,dispatch_get_global_queue等) 和 Block (^{...})。 -
Swift: 基于面向对象的类 (
DispatchQueue,DispatchGroup,DispatchSemaphore) 和方法调用 (.async { ... }) 以及更现代的闭包语法。API 命名更 Swifty。
-
OC: 基于 C 函数 (
-
线程保活: 原理相同(RunLoop + Source)。OC 常用
NSThread/NSRunLoop+performSelector:onThread:。Swift 可选择类似 OC 的方式(注意@objc)或更底层地使用DispatchQueue+CFRunLoopSource的方式。现代开发中,优先考虑使用专用串行DispatchQueue代替显式线程保活,除非有特殊需求。 - Swift 优势: 语法更简洁安全(闭包捕获列表),QoS 更语义化,API 设计更一致。
-
内存管理: Swift 中闭包捕获
self时需特别注意循环引用,使用[weak self]或[unowned self]。OC Block 同样需要注意循环引用(__weak typeof(self) weakSelf = self;)。
选择 OC 还是 Swift 编写 GCD 代码主要取决于项目语言本身。理解 GCD 的核心概念和 RunLoop 的保活机制是基础,语法差异相对容易适应。在 Swift 中,尽量使用其更现代、安全的语法特性。








网友评论