博文連接:http://ifujun.com/iosbing-fa-bian-cheng-tips-er/html
在iOS併發編程Tips(一)中,咱們提到了三點,分別是線程、原子屬性和併發同步。在本文中,你將會看到如下幾點:ios
線程安全編程
鎖數組
使用主線程安全
GCD 仍是 NSOperationQueue性能優化
線程安全是編程中的術語,指某個函數、函數庫在多線程環境中被調用時,可以正確地處理多個線程之間的共享變量,使程序功能正確完成。 — 維基百科網絡
舉個例子。多線程
咱們定義一個NSInteger
型的全局變量count
,咱們使用三個異步線程將它自增100000,那麼,咱們但願的輸出結果是300000。可是,它的真實結果是多少呢?併發
#import "ViewController.h" @interface ViewController () @property (assign, nonatomic) NSInteger count; @end @implementation ViewController -(void)viewDidLoad { [super viewDidLoad]; for (int i = 0; i < 3; i++) { [self startThread]; } } -(void)startThread { dispatch_async(dispatch_get_global_queue(0, 0), ^{ [self addCount]; }); } -(void)addCount { for (int i = 0; i < 100000; i++) { self.count++; } NSLog(@"count = %ld", self.count); } -(void)addCountWithLock { @synchronized (self) { for (int i = 0; i < 100000; i++) { self.count++; } NSLog(@"lock count = %ld", self.count); } } @end
運行結果顯然不是咱們想要的,並且,每次的結果都不必定一致,這就是咱們所要說的線程安全。app
不少時候,咱們爲了效率,會編寫多線程的代碼。多線程除了會帶來效率的提高以外,也會提升控制的複雜程度。咱們有不少解決辦法,好比說,使用鎖、不可變變量、儘可能使用主線程(單線程)等等。
在上述例子中,咱們若是加一個最簡單的互斥鎖(addCountWithLock
方法),就能夠達到線程安全的目的。
運行結果正是咱們想要的。
還有一點想說起一下的是,蘋果有個文檔列出了部分框架的部分安全和非安全的類和函數,能夠適當看一下。
上面提到了鎖,咱們經常使用的鎖有不少,好比,互斥鎖、條件鎖、遞歸鎖、信號量、自旋鎖等等。網上有不少關於這方面的資料,我就再也不贅述了,畢竟篇幅很大,而我這篇只是Tips。
網上也有不少關於這些鎖性能對比的文章,好比說ibireme的文章等等。
這麼多鎖,除了比較特殊的遞歸鎖等,若是你想要一個高性能的鎖的話,可使用pthread_mutex
或者dispatch_semaphore
,若是想使用比較方便的話,以直接使用@synchronized
和NSLock
。
在性能優化的時候,咱們很容易陷入過分優化的誤區。如今的設備性能愈來愈好,咱們能夠在主線程中作愈來愈多的事情。
若是某個函數或者方法只有主線程去訪問,那它必然是多線程安全的,由於只有單線程訪問,不存在多線程的狀況。
咱們知道NSMutableArray
、NSMutableDictionary
這種的是非線程安全的類,在個人使用過程當中,我通常不會對這些東西加鎖,由於我基本只用主線程去訪問,而若是涉及到多線程的話,我會使用不可變的數組和字典。
在大多數狀況下,使用多線程只存在於某一個部分,好比網絡等,那麼在多線程執行完成以後,必定要交由主線程回調。好比,咱們經常使用的AFNetworking
中,在回調success
和failure
的block塊的過程當中,就會回調到主線程上:
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{ success(self, responseObject); });
除了咱們本身設計的庫須要這麼作之外,也有一些系統上的方法須要咱們注意。好比,NSNotification
。
NSNotification
是哪一個線程去post就是哪一個線程去調用selector
。咱們來測試一下:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:kTestNotification object:nil]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:kTestNotification object:nil]; });
咱們在test
方法上打個斷點,咱們會看到:
這樣就會有問題,若是test
方法內是執行UI操做或者某些須要主線程的操做的話,那麼有可能會形成UI無響應,或者很長時間才變化,甚至是崩潰。
因此,我建議必定要在主線程上post,由於你不知道你所發出的NSNotification
誰會去接收,它又要去幹什麼,可是你知道,主線程是確定沒錯的。
實現這個的方法有不少,好比繼承、category、hook等。
前段時間在寫指紋解鎖的時候碰到一個問題。在個人App中須要驗證指紋或者手勢密碼才能夠進入主頁,而驗證指紋須要用到這麼一個方法:
-(void)evaluatePolicy:(LAPolicy)policy localizedReason:(NSString *)localizedReason reply:(void(^)(BOOL success, NSError * __nullable error))reply;
測試的時候,我發現一個問題,在用戶驗證經過以後,alertView
消失以後,頁面並無跳到主頁。有時候須要過很久纔會跳到主頁,可是頁面並無卡死,手勢解鎖依舊可用。這就奇了怪了,我找了一圈才發現,這個方法是在子線程上回調回來的,而我並不知道。因此我用這個子線程去初始化頁面的時候,就會出現長時間無響應的問題。
因此,系統異步回調的接口必定要去檢查一下是否是主線程的。
咱們知道,在 iOS 4 以上,NSOperationQueue
是在GCD上封裝上來的,相比起GCD,NSOperationQueue
具備以下一些優勢:
提供cancel操做。
更細粒度的優先級控制。
支持繼承,方便封裝。
支持KVO。
而GCD相比起NSOperationQueue
的優勢是:
使用方便、簡單。
速度可能更快一點。
我相信,對於大部分好的封裝來講,會優先選擇NSOperationQueue
。而若是你只是一個很小的項目,以使用方便爲主,那麼,使用GCD也是一種不錯的選擇。