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;
}
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
關鍵字 - 使用
NSLock
- 使用
NSRecursiveLock
- 使用
NSConditionLock
- 使用
NSCondition
類
GCD
方式
dispatch_semaphore
pthread_mutex
OSSpinLock
小結
- 單例的時候,就用
dispatch_once
,就不要用@synchronized
關鍵字 - 一般情況下,使用
@synchronized(self)
關鍵字,雖然性能耗一點,大多數(shù)情況下夠用了,簡單好用 - 接下來,考慮的是
NSOperation
和NSOperationQueue
,并發(fā)數(shù)為1就是串行隊列,線程安全;或者設置下依賴,不用管同步異步,串行并行等事情 - 再接下來,可以考慮用
GCD
的同步執(zhí)行和柵欄,就像前面例子提到的那樣,weex
中用的那樣,線程安全的字典或者數(shù)組,可以作為參考 - 再接下來,可以考慮用
NSLock
、NSRecursiveLock
、NSConditionLock
,用到這些的,已經(jīng)算比較復雜了,要注意死鎖問題,時序是否按照預想的發(fā)展。沒有必要,還是不要用比較好。 - 如果考慮性能問題,那就用最后的大招
dispatch_semaphore
、
pthread_mutex
、OSSpinLock