ios中如何進行線程間通信

在 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_syncdispatch_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@synchronizedpthread_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. 使用 NSMachPortCFMessagePort

NSMachPortCFMessagePort 是基于 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);
}

注意事項:

  • NSMachPortCFMessagePort 是相對底層的機制,適合需要高性能或復(fù)雜通信的場景。

總結(jié)

在 iOS 開發(fā)中,線程間通信有多種方式,選擇哪種方式取決于具體的需求:

  • 簡單任務(wù)調(diào)度:使用 GCD(dispatch_async)是最簡單和推薦的方式。
  • UI 更新:確保 UI 更新在主線程上執(zhí)行,使用 dispatch_async(dispatch_get_main_queue(), ^{...})
  • 任務(wù)依賴:使用 NSOperationQueue 來管理任務(wù)依賴和取消。
  • 低級通信:如果需要更底層的線程間通信,可以使用 NSMachPortCFMessagePort
  • 通知機制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ù)。如果你需要傳遞非對象類型(如 intfloat 等),可以通過 NSNumberNSValue 進行包裝。

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 通常是更好的選擇。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,238評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,339評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,713評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,893評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,448評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,201評論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,397評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,631評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,128評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,347評論 2 377

推薦閱讀更多精彩內(nèi)容