在 iOS 開發(fā)中,線程間通信是一個常見的需求。由于 UI 更新必須在主線程上執(zhí)行,而耗時任務(wù)通常需要放在后臺線程中處理,因此我們需要一種機制來在不同線程之間傳遞消息或數(shù)據(jù)。iOS 提供了多種方式進行線程間通信,以下是幾種常見的方式:
1. 使用 performSelector:onThread:
performSelector:onThread:
是 Objective-C 中的一種跨線程通信方式,允許你在指定的線程上執(zhí)行某個方法。
示例代碼:
// 在后臺線程中執(zhí)行任務(wù)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 模擬耗時任務(wù)
NSLog(@"后臺線程處理任務(wù)");
// 在主線程上更新 UI
[self performSelector:@selector(updateUI)
onThread:[NSThread mainThread]
withObject:nil
waitUntilDone:NO];
});
// 在主線程上更新 UI
- (void)updateUI {
NSLog(@"在主線程上更新 UI");
// 更新 UI 的代碼
}
注意事項:
- 目標(biāo)線程必須有運行的 RunLoop。
- 只能傳遞一個對象類型的參數(shù)。
- 如果需要傳遞多個參數(shù),可以使用
NSDictionary
或自定義對象。
2. 使用 GCD(Grand Central Dispatch)
GCD 是蘋果推薦的多線程管理工具,提供了簡潔的 API 來進行線程間的任務(wù)調(diào)度。GCD 可以輕松地將任務(wù)調(diào)度到主線程或后臺線程。
2.1 調(diào)度到主線程
如果你在后臺線程中處理了一些數(shù)據(jù),并且需要在主線程上更新 UI,可以使用 dispatch_async
將任務(wù)調(diào)度到主線程。
// 在后臺線程中執(zhí)行任務(wù)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 模擬耗時任務(wù)
NSLog(@"后臺線程處理任務(wù)");
// 在主線程上更新 UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"在主線程上更新 UI");
// 更新 UI 的代碼
});
});
2.2 調(diào)度到后臺線程
你可以使用 dispatch_async
將任務(wù)調(diào)度到后臺線程。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 在后臺線程上執(zhí)行任務(wù)
NSLog(@"后臺線程執(zhí)行任務(wù)");
});
2.3 使用 dispatch_sync
和 dispatch_async
-
dispatch_async
: 異步執(zhí)行任務(wù),不會阻塞當(dāng)前線程。 -
dispatch_sync
: 同步執(zhí)行任務(wù),會阻塞當(dāng)前線程,直到任務(wù)完成。
// 異步執(zhí)行任務(wù)
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"異步執(zhí)行任務(wù)");
});
// 同步執(zhí)行任務(wù)
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"同步執(zhí)行任務(wù)");
});
注意事項:
- 避免在主線程上調(diào)用
dispatch_sync
,否則可能會導(dǎo)致死鎖。
3. 使用 NSOperationQueue
NSOperationQueue
是另一種高級的多線程管理工具,它是基于 GCD 的封裝,提供了更多的控制和靈活性。
示例代碼:
// 創(chuàng)建一個操作隊列
NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
// 在后臺線程中執(zhí)行任務(wù)
[backgroundQueue addOperationWithBlock:^{
NSLog(@"后臺線程處理任務(wù)");
// 在主線程上更新 UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"在主線程上更新 UI");
// 更新 UI 的代碼
}];
}];
注意事項:
-
NSOperationQueue
提供了更多的功能,例如任務(wù)依賴、取消任務(wù)等。 - 主線程的操作隊列可以通過
[NSOperationQueue mainQueue]
獲取。
4. 使用 NotificationCenter
NSNotificationCenter
可以用于在同一個進程內(nèi)的不同線程之間傳遞消息。雖然它本身不支持跨線程通信,但你可以結(jié)合 performSelector:onThread:
或 GCD 來實現(xiàn)跨線程通信。
示例代碼:
// 注冊通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleNotification:)
name:@"MyNotification"
object:nil];
// 在后臺線程中發(fā)送通知
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"MyNotification" object:nil];
});
// 處理通知
- (void)handleNotification:(NSNotification *)notification {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"在主線程中處理通知: %@", notification);
});
}
注意事項:
-
NSNotificationCenter
默認是線程安全的,但通知的處理邏輯可能不是。確保通知的處理邏輯在正確的線程上執(zhí)行。
5. 使用信號量(Semaphore)
信號量是一種同步機制,可以用來協(xié)調(diào)線程之間的執(zhí)行順序。dispatch_semaphore_t
是 GCD 提供的信號量類型。
示例代碼:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// 在后臺線程中執(zhí)行任務(wù)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"后臺線程處理任務(wù)");
// 任務(wù)完成后釋放信號量
dispatch_semaphore_signal(semaphore);
});
// 等待信號量
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任務(wù)完成,繼續(xù)執(zhí)行");
注意事項:
- 信號量可以用于線程同步,但要小心避免死鎖。
6. 使用鎖(Locks)
在多線程環(huán)境中,共享資源的訪問需要加鎖以避免競爭條件。iOS 提供了多種鎖機制,如 NSLock
、@synchronized
、pthread_mutex
等。
示例代碼:
NSLock *lock = [[NSLock alloc] init];
// 線程 1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSLog(@"線程 1 鎖定資源");
// 訪問共享資源
[lock unlock];
});
// 線程 2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSLog(@"線程 2 鎖定資源");
// 訪問共享資源
[lock unlock];
});
注意事項:
- 加鎖會影響性能,盡量減少鎖的范圍。
- 避免死鎖,確保鎖的獲取和釋放順序一致。
7. 使用 Block 回調(diào)
你可以在后臺線程中執(zhí)行任務(wù),并通過 Block 回調(diào)將結(jié)果傳遞回主線程。
示例代碼:
// 定義一個帶有回調(diào)的函數(shù)
- (void)doBackgroundTaskWithCompletion:(void (^)(NSString *))completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 模擬耗時任務(wù)
NSString *result = @"后臺任務(wù)完成";
// 回調(diào)到主線程
dispatch_async(dispatch_get_main_queue(), ^{
completion(result);
});
});
}
// 調(diào)用帶有回調(diào)的任務(wù)
[self doBackgroundTaskWithCompletion:^(NSString *result) {
NSLog(@"任務(wù)完成,結(jié)果: %@", result);
}];
注意事項:
- Block 回調(diào)是一種非常靈活的方式,適用于簡單的任務(wù)結(jié)果傳遞。
8. 使用 NSMachPort
或 CFMessagePort
NSMachPort
和 CFMessagePort
是基于 Mach Port 的低級線程間通信機制,適用于更復(fù)雜的線程間通信場景。
示例代碼(NSMachPort):
// 創(chuàng)建 Mach Port
NSMachPort *machPort = [NSMachPort port];
machPort.delegate = self;
// 將 Mach Port 添加到 RunLoop
[[NSRunLoop currentRunLoop] addPort:machPort forMode:NSDefaultRunLoopMode];
// 發(fā)送消息
struct {
mach_msg_header_t header;
} message;
message.header.msgh_remote_port = (mach_port_t)[machPort machPort];
message.header.msgh_local_port = MACH_PORT_NULL;
message.header.msgh_id = 12345;
kern_return_t result = mach_msg(&message.header,
MACH_SEND_MSG,
sizeof(message),
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (result != KERN_SUCCESS) {
NSLog(@"消息發(fā)送失敗: %s", mach_error_string(result));
}
// 處理消息
- (void)handleMachMessage:(void *)msg {
mach_msg_header_t *header = (mach_msg_header_t *)msg;
NSLog(@"接收到 Mach 消息,消息 ID: %d", header->msgh_id);
}
注意事項:
-
NSMachPort
和CFMessagePort
是相對底層的機制,適合需要高性能或復(fù)雜通信的場景。
總結(jié)
在 iOS 開發(fā)中,線程間通信有多種方式,選擇哪種方式取決于具體的需求:
-
簡單任務(wù)調(diào)度:使用 GCD(
dispatch_async
)是最簡單和推薦的方式。 -
UI 更新:確保 UI 更新在主線程上執(zhí)行,使用
dispatch_async(dispatch_get_main_queue(), ^{...})
。 -
任務(wù)依賴:使用
NSOperationQueue
來管理任務(wù)依賴和取消。 -
低級通信:如果需要更底層的線程間通信,可以使用
NSMachPort
或CFMessagePort
。 -
通知機制:
NSNotificationCenter
可以用于同一進程內(nèi)的線程間通信。
每種方式都有其適用的場景,開發(fā)者應(yīng)根據(jù)實際需求選擇合適的線程間通信方式。
performSelector:onThread:
performSelector:onThread:
是 Objective-C 中的一個方法,用于在指定的線程上執(zhí)行某個選擇器(selector)。它允許你將某個方法調(diào)用分發(fā)到特定的線程上執(zhí)行,通常用于跨線程通信或確保某些代碼在特定線程(例如主線程)上運行。
1. performSelector:onThread:
的基本語法
- (void)performSelector:(SEL)aSelector
onThread:(NSThread *)thread
withObject:(id)arg
waitUntilDone:(BOOL)wait;
參數(shù)說明:
aSelector: 要執(zhí)行的方法的選擇器(selector),該方法必須接受一個參數(shù)并返回
void
。thread: 指定要在哪個線程上執(zhí)行該方法。通常你會傳入主線程或其他后臺線程。
arg: 傳遞給目標(biāo)方法的參數(shù)。如果不需要傳遞參數(shù),可以傳入
nil
。wait: 是否等待當(dāng)前線程阻塞,直到目標(biāo)方法執(zhí)行完畢。如果為
YES
,當(dāng)前線程會阻塞,直到目標(biāo)方法執(zhí)行完成;如果為NO
,當(dāng)前線程不會等待,目標(biāo)方法會在指定線程上異步執(zhí)行。
2. 使用場景
performSelector:onThread:
主要用于以下場景:
跨線程通信:當(dāng)你需要在不同的線程之間傳遞消息或執(zhí)行某些操作時,可以使用
performSelector:onThread:
來確保代碼在正確的線程上執(zhí)行。確保主線程執(zhí)行:在 iOS 開發(fā)中,UI 更新必須在主線程上執(zhí)行。如果你在后臺線程中處理了一些數(shù)據(jù),并且需要更新 UI,可以使用
performSelector:onThread:
將 UI 更新操作調(diào)度到主線程。
3. 示例代碼
3.1 在主線程上執(zhí)行方法
假設(shè)你在一個后臺線程中處理了一些數(shù)據(jù),并且需要在主線程上更新 UI。你可以使用 performSelector:onThread:
將 UI 更新操作調(diào)度到主線程。
// 假設(shè)這是在后臺線程中執(zhí)行的代碼
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 處理一些耗時任務(wù)
NSLog(@"后臺線程處理任務(wù)");
// 在主線程上更新 UI
[self performSelector:@selector(updateUI)
onThread:[NSThread mainThread]
withObject:nil
waitUntilDone:NO];
});
// 在主線程上更新 UI
- (void)updateUI {
NSLog(@"在主線程上更新 UI");
// 更新 UI 的代碼
}
在這個例子中,updateUI
方法會被調(diào)度到主線程上執(zhí)行,確保 UI 更新操作在主線程上進行。
3.2 在自定義線程上執(zhí)行方法
你也可以在自定義的線程上執(zhí)行方法。首先,你需要創(chuàng)建一個新的線程,并確保該線程的 RunLoop 正在運行。
// 創(chuàng)建一個新的線程
NSThread *customThread = [[NSThread alloc] initWithTarget:self
selector:@selector(threadMainMethod)
object:nil];
[customThread start];
// 線程的主方法
- (void)threadMainMethod {
@autoreleasepool {
// 啟動 RunLoop
[[NSRunLoop currentRunLoop] run];
}
}
// 在后臺線程中發(fā)送通知
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 在自定義線程上執(zhí)行方法
[self performSelector:@selector(doSomething)
onThread:customThread
withObject:nil
waitUntilDone:NO];
});
// 自定義線程上執(zhí)行的任務(wù)
- (void)doSomething {
NSLog(@"在自定義線程上執(zhí)行任務(wù)");
}
在這個例子中,我們創(chuàng)建了一個自定義線程,并確保它的 RunLoop 正在運行。然后,我們使用 performSelector:onThread:
將 doSomething
方法調(diào)度到這個自定義線程上執(zhí)行。
4. 注意事項
4.1 必須有 RunLoop
performSelector:onThread:
要求目標(biāo)線程的 RunLoop 正在運行,否則方法不會被執(zhí)行。因此,在使用 performSelector:onThread:
之前,確保目標(biāo)線程的 RunLoop 已經(jīng)啟動。
[[NSRunLoop currentRunLoop] run];
4.2 只能傳遞一個參數(shù)
performSelector:onThread:
只能傳遞一個參數(shù)。如果你需要傳遞多個參數(shù),可以考慮使用 NSDictionary
或自定義對象來封裝多個參數(shù)。
NSDictionary *userInfo = @{@"key1": @"value1", @"key2": @"value2"};
[self performSelector:@selector(handleNotification:)
onThread:[NSThread mainThread]
withObject:userInfo
waitUntilDone:NO];
- (void)handleNotification:(NSDictionary *)userInfo {
NSLog(@"接收到通知: %@", userInfo);
}
4.3 不支持 ARC 下的非對象類型
在 ARC(自動引用計數(shù))環(huán)境下,performSelector:onThread:
只能傳遞對象類型的參數(shù)。如果你需要傳遞非對象類型(如 int
、float
等),可以通過 NSNumber
或 NSValue
進行包裝。
NSNumber *number = @(42);
[self performSelector:@selector(handleNumber:)
onThread:[NSThread mainThread]
withObject:number
waitUntilDone:NO];
- (void)handleNumber:(NSNumber *)number {
NSLog(@"接收到數(shù)字: %d", [number intValue]);
}
4.4 避免死鎖
如果你將 waitUntilDone
設(shè)置為 YES
,當(dāng)前線程會阻塞,直到目標(biāo)線程上的方法執(zhí)行完畢。如果目標(biāo)線程正在等待當(dāng)前線程完成某些操作,可能會導(dǎo)致死鎖。因此,謹慎使用 waitUntilDone:YES
。
5. 替代方案
雖然 performSelector:onThread:
是一種非常方便的方式,但在現(xiàn)代開發(fā)中,GCD(Grand Central Dispatch)通常是更推薦的跨線程通信方式。GCD 提供了更簡潔和靈活的 API 來處理多線程任務(wù)。
使用 GCD 調(diào)度到主線程
dispatch_async(dispatch_get_main_queue(), ^{
// 在主線程上執(zhí)行任務(wù)
[self updateUI];
});
使用 GCD 調(diào)度到后臺線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 在后臺線程上執(zhí)行任務(wù)
[self doSomething];
});
6. 總結(jié)
performSelector:onThread:
是一種在指定線程上執(zhí)行方法的機制,適用于跨線程通信或確保某些代碼在特定線程上執(zhí)行。使用場景:主要用于跨線程通信,尤其是確保 UI 更新操作在主線程上執(zhí)行。
注意事項:目標(biāo)線程必須有運行的 RunLoop,且只能傳遞一個對象類型的參數(shù)。
替代方案:在現(xiàn)代開發(fā)中,GCD 是更推薦的跨線程通信方式,提供了更簡潔和靈活的 API。
總之,performSelector:onThread:
是一種有效的跨線程通信工具,但在現(xiàn)代開發(fā)中,GCD 通常是更好的選擇。