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;
}
NSString
和UTF8String
是不一樣的,GCD
是c
函數(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ù)情況下夠用了导狡,簡單好用 - 接下來旱捧,考慮的是
NSOperation
和NSOperationQueue
枚赡,并發(fā)數(shù)為1就是串行隊列贫橙,線程安全卢肃;或者設(shè)置下依賴莫湘,不用管同步異步幅垮,串行并行等事情 - 再接下來巩螃,可以考慮用
GCD
的同步執(zhí)行和柵欄匕争,就像前面例子提到的那樣,weex
中用的那樣跑杭,線程安全的字典或者數(shù)組爹橱,可以作為參考 - 再接下來愧驱,可以考慮用
NSLock
组砚、NSRecursiveLock
、NSConditionLock
盆偿,用到這些的,已經(jīng)算比較復(fù)雜了,要注意死鎖問題,時序是否按照預(yù)想的發(fā)展。沒有必要溯乒,還是不要用比較好。 - 如果考慮性能問題豹爹,那就用最后的大招
dispatch_semaphore
裆悄、
pthread_mutex
、OSSpinLock