iOS線程安全

UIKit是線程不安全的

  • UIKit是線程不安全的斩萌,并且這是蘋果有意的設(shè)計入挣,主要是為了提升性能说铃。具體原因柔滔,下面這篇文章寫得很好:
    線程安全類的設(shè)計

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

  • 最容易犯的錯誤是在后臺線程中對property賦值,比如圖片朋腋,因為圖片是在后臺從網(wǎng)絡(luò)上獲取的齐疙。如果兩個線程同時設(shè)置圖片,很可能程序?qū)⒅苯颖罎⑿裱剩驗楫斍霸O(shè)置的圖片可能會被釋放兩次贞奋。由于這是和時機相關(guān)的,因此崩潰通常發(fā)生在客戶使用時轻专,而并不是在開發(fā)過程中忆矛。
  • 在大多數(shù)情況下,UIKit類只應(yīng)該在程序的主線程使用。無論是從UIResponder派生的類催训,還是那些涉及以任何方式操作你的應(yīng)用程序的用戶界面洽议。
  • 在異步塊操作里一貫使用__weak和不訪問ivars是推薦的方式。
  • 一般情況下漫拭,不可變類亚兄,像NSArray是線程安全的,而它們的可變的變體采驻,像NSMutableArray則不是审胚。好的做法是寫一些像return [array copy]來確保返回的對象實際上是不可變的。
  • 單獨使用原子屬性不會讓你的類線程安全的礼旅。它只會保護你在setter中免受競態(tài)條件(race conditions)膳叨,但不會保護你的應(yīng)用程序邏輯。
  • 在試圖做線程安全之前痘系,認真考慮是否是必要的菲嘴。請確保它不是過早的優(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,在其他地方定義佛纫,應(yīng)該是個內(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過來跟磨,不過要重新設(shè)計一下對外的接口间聊。本人更傾向于這種組合模式攒盈,接口可以自定義,按照需求來哎榴,用特殊的名字型豁,防止使用過度。更有定制化的味道尚蝌。比如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關(guān)鍵字
  • 使用NSLock
  • 使用NSRecursiveLock
  • 使用NSConditionLock
  • 使用NSCondition

GCD方式

  • dispatch_semaphore
  • pthread_mutex
  • OSSpinLock

小結(jié)

  • 單例的時候,就用dispatch_once蒜鸡,就不要用@synchronized關(guān)鍵字
  • 一般情況下逢防,使用@synchronized(self)關(guān)鍵字恬汁,雖然性能耗一點,大多數(shù)情況下夠用了导狡,簡單好用
  • 接下來旱捧,考慮的是NSOperationNSOperationQueue枚赡,并發(fā)數(shù)為1就是串行隊列贫橙,線程安全卢肃;或者設(shè)置下依賴莫湘,不用管同步異步幅垮,串行并行等事情
  • 再接下來巩螃,可以考慮用GCD的同步執(zhí)行和柵欄匕争,就像前面例子提到的那樣,weex中用的那樣跑杭,線程安全的字典或者數(shù)組爹橱,可以作為參考
  • 再接下來愧驱,可以考慮用NSLock组砚、NSRecursiveLockNSConditionLock盆偿,用到這些的,已經(jīng)算比較復(fù)雜了,要注意死鎖問題,時序是否按照預(yù)想的發(fā)展。沒有必要溯乒,還是不要用比較好。
  • 如果考慮性能問題豹爹,那就用最后的大招dispatch_semaphore裆悄、
    pthread_mutexOSSpinLock
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末臂聋,一起剝皮案震驚了整個濱河市光稼,隨后出現(xiàn)的幾起案子或南,更是在濱河造成了極大的恐慌,老刑警劉巖艾君,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件采够,死亡現(xiàn)場離奇詭異,居然都是意外死亡冰垄,警方通過查閱死者的電腦和手機蹬癌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虹茶,“玉大人逝薪,你說我怎么就攤上這事⌒纯荆” “怎么了翼闽?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長洲炊。 經(jīng)常有香客問我感局,道長,這世上最難降的妖魔是什么暂衡? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任询微,我火速辦了婚禮,結(jié)果婚禮上狂巢,老公的妹妹穿的比我還像新娘撑毛。我一直安慰自己,他們只是感情好唧领,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布藻雌。 她就那樣靜靜地躺著,像睡著了一般斩个。 火紅的嫁衣襯著肌膚如雪胯杭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天受啥,我揣著相機與錄音做个,去河邊找鬼。 笑死滚局,一個胖子當著我的面吹牛居暖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播藤肢,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼太闺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了谤草?” 一聲冷哼從身側(cè)響起跟束,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤莺奸,失蹤者是張志新(化名)和其女友劉穎丑孩,沒想到半個月后冀宴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡温学,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年略贮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仗岖。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡逃延,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出轧拄,到底是詐尸還是另有隱情揽祥,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布檩电,位于F島的核電站拄丰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏俐末。R本人自食惡果不足惜料按,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卓箫。 院中可真熱鬧载矿,春花似錦、人聲如沸烹卒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旅急。三九已至逢勾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坠非,已是汗流浹背敏沉。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炎码,地道東北人盟迟。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像潦闲,于是被迫代替她去往敵國和親攒菠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

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

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