iOS線程安全

UIKit是線程不安全的

  • UIKit是線程不安全的,并且這是蘋果有意的設計,主要是為了提升性能。具體原因,下面這篇文章寫得很好:
    線程安全類的設計

這個章節(jié)的內(nèi)容都是這篇文章的節(jié)選

  • 最容易犯的錯誤是在后臺線程中對property賦值,比如圖片,因為圖片是在后臺從網(wǎng)絡上獲取的。如果兩個線程同時設置圖片,很可能程序?qū)⒅苯颖罎ⅲ驗楫斍霸O置的圖片可能會被釋放兩次。由于這是和時機相關的,因此崩潰通常發(fā)生在客戶使用時,而并不是在開發(fā)過程中。
  • 在大多數(shù)情況下,UIKit類只應該在程序的主線程使用。無論是從UIResponder派生的類,還是那些涉及以任何方式操作你的應用程序的用戶界面。
  • 在異步塊操作里一貫使用__weak和不訪問ivars是推薦的方式。
  • 一般情況下,不可變類,像NSArray是線程安全的,而它們的可變的變體,像NSMutableArray則不是。好的做法是寫一些像return [array copy]來確保返回的對象實際上是不可變的。
  • 單獨使用原子屬性不會讓你的類線程安全的。它只會保護你在setter中免受競態(tài)條件(race conditions),但不會保護你的應用程序邏輯。
  • 在試圖做線程安全之前,認真考慮是否是必要的。請確保它不是過早的優(yōu)化。如果它像是一個配置類,考慮線程安全是沒有意義的。更好的方法是拋出一些斷言來確保它的正確使用:
void PSPDFAssertIfNotMainThread(void) { 
    NSAssert(NSThread.isMainThread, 
      @"Error: Method needs to be called on the main thread. %@", 
      [NSThread callStackSymbols]); 
} 
  • 一個好的方法是使用一個并行dispatch_queue為讀/寫鎖,以最大限度地提高性能,并嘗試只鎖定那些真正需要的地方。
// header 
@property (nonatomic, strong) NSMutableSet *delegates; 
 
// in init 
_delegateQueue = dispatch_queue_create("com.PSPDFKit.cacheDelegateQueue", 
  DISPATCH_QUEUE_CONCURRENT); 
 
- (void)addDelegate:(id<PSPDFCacheDelegate>)delegate { 
    dispatch_barrier_async(_delegateQueue, ^{ 
        [self.delegates addObject:delegate]; 
    }); 
} 
 
- (void)removeAllDelegates { 
    dispatch_barrier_async(_delegateQueue, ^{ 
        self.delegates removeAllObjects]; 
    }); 
} 
 
- (void)callDelegateForX { 
    dispatch_sync(_delegateQueue, ^{ 
        [self.delegates enumerateObjectsUsingBlock:^(id<PSPDFCacheDelegate> delegate, NSUInteger idx, BOOL *stop) { 
            // Call delegate 
        }]; 
    }); 
} 

_delegateQueue的類型是 dispatch_queue_t,在其他地方定義,應該是個內(nèi)部成員變量

  • 除非addDelegate:removeDelegate:每秒被調(diào)用上千次,否則下面是更簡潔的方法:
// header 
@property (atomic, copy) NSSet *delegates; 
 
- (void)addDelegate:(id<PSPDFCacheDelegate>)delegate { 
    @synchronized(self) { 
        self.delegates = [self.delegates setByAddingObject:delegate]; 
    } 
} 
 
- (void)removeAllDelegates { 
    self.delegates = nil; 
} 
 
- (void)callDelegateForX { 
    [self.delegates enumerateObjectsUsingBlock:^(id<PSPDFCacheDelegate> delegate, NSUInteger idx, BOOL *stop) { 
        // Call delegate 
    }]; 
} 

實際的例子

在weex的SDK中有線程安全的字典和數(shù)組,以字典為例:

  • 采用繼承現(xiàn)有的字典方式
/**
 *  @abstract Thread safe NSMutableDictionary
 */
@interface WXThreadSafeMutableDictionary<KeyType, ObjectType> : NSMutableDictionary

@end
  • 這個可以討論,可以直接從NSObject過來,不過要重新設計一下對外的接口。本人更傾向于這種組合模式,接口可以自定義,按照需求來,用特殊的名字,防止使用過度。更有定制化的味道。比如key規(guī)定為NSString類型
  • 用繼承的方式,直接重寫父類的方法。這種方式是接口更通用,而且不用自己想接口的名字和調(diào)用方式。使用起來,跟普通的字典沒什么兩樣。使用起來更方便。

  • 內(nèi)部用了一個隊列,額外包含了一個字典

@interface WXThreadSafeMutableDictionary ()

@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, strong) NSMutableDictionary* dict;

@end
  • 是并行隊列,用到了指針地址作為名字的一部分,保證唯一性
- (instancetype)initCommon {
    self = [super init];
    if (self) {
        NSString* uuid = [NSString stringWithFormat:@"com.taobao.weex.dictionary_%p", self];
        _queue = dispatch_queue_create([uuid UTF8String], DISPATCH_QUEUE_CONCURRENT);
    }
    return self;
}

NSStringUTF8String是不一樣的,GCDc函數(shù)

  • 有些方法用同步執(zhí)行的方式實現(xiàn)“線程安全”
- (id)objectForKey:(id)aKey {
    __block id obj;
    dispatch_sync(_queue, ^{
        obj = _dict[aKey];
    });
    return obj;
}
  • 有些方法用異步柵欄實現(xiàn)“線程安全”
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey {
    aKey = [aKey copyWithZone:NULL];
    dispatch_barrier_async(_queue, ^{
        _dict[aKey] = anObject;
    });
}
  • copy給的是內(nèi)部成員變量字典的副本,并不是真正自己的副本。這個就是本人不大喜歡繼承的原因。(很多時候,組合比繼承要好理解一點)
- (id)copy {
    __block id copyInstance;
    dispatch_sync(_queue, ^{
        copyInstance = [_dict copy];
    });
    return copyInstance;
}

同步機制

保證線程安全的措施。下面兩篇文章不錯:
iOS中保證線程安全的幾種方式與性能對比
iOS開發(fā)里的線程安全機制

Foundation對象

  • 使用@synchronized關鍵字
  • 使用NSLock
  • 使用NSRecursiveLock
  • 使用NSConditionLock
  • 使用NSCondition

GCD方式

  • dispatch_semaphore
  • pthread_mutex
  • OSSpinLock

小結

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

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

  • iOS線程安全的鎖與性能對比 一、鎖的基本使用方法 1.1、@synchronized 這是我們最熟悉的枷鎖方式,...
    Jacky_Yang閱讀 2,247評論 0 17
  • GCD簡介 GCD 是 libdispatch 的市場名稱,而 libdispatch 作為 Apple 的一個庫...
    獨木舟的木閱讀 1,273評論 0 5
  • Object C中創(chuàng)建線程的方法是什么?如果在主線程中執(zhí)行代碼,方法是什么?如果想延時執(zhí)行代碼、方法又是什么? 1...
    AlanGe閱讀 1,765評論 0 17
  • 多線程的安全隱患 當多個線程訪問同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題 安全隱患解決 方案一:使用“同步塊...
    AYuan_閱讀 746評論 0 3
  • 為什么CFRunLoopRef是線程安全的,而基于此的NSRunLoop卻不是線程安全的呢? 線程安全時多線程領域...
    小貓仔閱讀 1,203評論 2 4