偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

主線程中也不絕對安全的UI操作

移動開發(fā) iOS
從最初開始學(xué)習(xí) iOS 的時候,我們就被告知 UI 操作一定要放在主線程進行。這是因為 UIKit 的方法不是線程安全的,保證線程安全需要極大的開銷。那么問題來了,在主線程中進行 UI 操作一定是安全的么?

[[185244]]

從最初開始學(xué)習(xí) iOS 的時候,我們就被告知 UI 操作一定要放在主線程進行。這是因為 UIKit 的方法不是線程安全的,保證線程安全需要極大的開銷。那么問題來了,在主線程中進行 UI 操作一定是安全的么?

顯然,答案是否定的!

在蘋果的 MapKit 框架中,有一個叫做 addOverlay 的方法,它在底層實現(xiàn)的時候,不僅僅要求代碼執(zhí)行在主線程上,還要求執(zhí)行在 GCD 的主隊列上。這是一個極罕見的問題,但已經(jīng)有人在使用 ReactiveCocoa 時踩到了坑,并提交了 issue。

蘋果的 Developer Technology Support 承認這是一個 bug。不管這是 bug 還是歷史遺留設(shè)計,也不管是不是在鉆牛角尖,為了避免再次掉進同樣的坑,我認為都有必要分析一下問題發(fā)生的原因和解決方案。

GCD 知識復(fù)習(xí)

在 GCD 中,使用 dispatch_get_main_queue() 函數(shù)可以獲取主隊列。調(diào)用 dispatch_sync() 方法會把任務(wù)同步提交到指定的隊列。

注意一下隊列和線程的區(qū)別,他們之間并沒有“擁有關(guān)系(ownership)”,當(dāng)我們同步的提交一個任務(wù)時,首先會阻塞當(dāng)前隊列,然后等到下一次 runloop 時再在合適的線程中執(zhí)行 block。

在執(zhí)行 block 之前,首先會尋找合適的線程來執(zhí)行block,然后阻塞這個線程,直到 block 執(zhí)行完畢。尋找線程的規(guī)則是: 任何提交到主隊列的 block 都會在主線程中執(zhí)行,在不違背此規(guī)則的前提下,文檔還告訴我們系統(tǒng)會自動進行優(yōu)化,盡可能的在當(dāng)前線程執(zhí)行 block。

順便補充一句,GCD 死鎖的充分條件是:“向當(dāng)前隊列重復(fù)同步提交 block”。從原理來看,死鎖的原因是提交的 block 阻塞了隊列,而隊列阻塞后永遠無法執(zhí)行完 dispatch_sync(),可見這里完全和代碼所在的線程無關(guān)。

另一個例子也可以證明這一點,在主線程中向一個串行隊列同步的派發(fā) block,根據(jù)上文選擇線程的原則,block 將在主線程中執(zhí)行,但同樣不會導(dǎo)致死鎖:

  1. dispatch_queue_t queue = dispatch_queue_create("com.kt.deadlock", nil); 
  2. dispatch_sync(queue, ^{ 
  3.     NSLog(@"current thread = %@", [NSThread currentThread]); 
  4. }); 
  5. // 輸出結(jié)果: 
  6. // current thread = {number = 1, name = main}  

原因分析

啰嗦了這么多,回到之前描述的 bug 中來?,F(xiàn)在我們知道,即使是在主線程中執(zhí)行的代碼,也很可能不是運行在主隊列中(反之則必然)。如果我們在子隊列中調(diào)用 MapKit 的 addOverlay 方法,即使當(dāng)前處于主線程,也會導(dǎo)致 bug 的產(chǎn)生,因為這個方法的底層實現(xiàn)判斷的是主隊列而非主線程。

更進一步的思考,有時候為了保證 UI 操作在主線程運行,如果有一個函數(shù)可以用來創(chuàng)建新的 UILabel,為了確保線程安全,代碼可能是這樣:

  1. - (UILabel *)labelWithText: (NSString *)text { 
  2.     __block UILabel *theLabel; 
  3.     if ([NSThread isMainThread]) { 
  4.         theLabel = [[UILabel alloc] init]; 
  5.         [theLabel setText:text]; 
  6.     } 
  7.     else { 
  8.         dispatch_sync(dispatch_get_main_queue(), ^{ 
  9.             theLabel = [[UILabel alloc] init]; 
  10.             [theLabel setText:text]; 
  11.         }); 
  12.     } 
  13.     return theLabel; 
  14.  

從嚴(yán)格意義上來講,這樣的寫法不是 100% 安全的,因為我們無法得知相關(guān)的系統(tǒng)方法是否存在上述 Bug。

解決方案

由于提交到主隊列的 block 一定在主線程運行,并且在 GCD 中線程切換通常都是由指定某個隊列引起的,我們可以做一個更加嚴(yán)格的判斷,即用判斷是否處于主隊列來代替是否處于主線程。

GCD 沒有提供 API 來進行相應(yīng)的判斷,但我們可以另辟蹊徑,利用 dispatch_queue_set_specific 和 dispatch_get_specific 這一組方法為主隊列打上標(biāo)記:

  1. + (BOOL)isMainQueue { 
  2.     static const void* mainQueueKey = @"mainQueue"
  3.     static void* mainQueueContext = @"mainQueue"
  4.   
  5.     static dispatch_once_t onceToken; 
  6.     dispatch_once(&onceToken, ^{ 
  7.         dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, mainQueueContext, nil); 
  8.     }); 
  9.   
  10.     return dispatch_get_specific(mainQueueKey) == mainQueueContext; 
  11.  

用 isMainQueue 方法代替 [NSThread isMainThread] 即可獲得更好的安全性。

參考資料

1.Community bug reports about MapKit

   http://t.cn/RtxivSc

2.GCD’s Main Queue vs Main Thread

   http://t.cn/RthOawx

3.ReactiveCocoa 中遇到類似的坑

   http://t.cn/RtxJFRX

4.Why can’t we use a dispatch_sync on the current queue?

   http://t.cn/RtxJgPi 

責(zé)任編輯:龐桂玉 來源: iOS大全
相關(guān)推薦

2015-08-07 10:40:31

UI主線程

2012-05-14 17:09:05

iOS

2014-04-08 14:19:06

Android開發(fā)UI線程

2016-10-21 13:03:18

androidhandlerlooper

2011-05-27 10:19:42

2009-04-30 09:10:42

JavaSwing線程安全

2024-05-21 08:22:10

線程主線程程序

2015-11-05 12:02:10

2024-05-16 12:51:15

WinForms線程UI

2022-05-19 10:04:15

UIAndroid子線程

2011-06-22 15:42:18

QT 信號

2014-06-18 14:41:26

AndroidHandler總結(jié)

2010-03-15 18:34:08

Java多線程

2024-04-03 08:25:11

DictionaryC#字典類型

2022-09-16 06:59:49

api線程安全

2010-02-24 11:19:00

Python主線程

2011-05-13 12:37:30

2012-05-04 09:49:34

進程

2013-04-02 13:56:12

UI線程處理位圖

2017-01-03 17:57:46

Android異步精髓Handler
點贊
收藏

51CTO技術(shù)棧公眾號