iOS併發編程Tips(二)

博文連接: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,若是想使用比較方便的話,以直接使用@synchronizedNSLock

使用主線程

在性能優化的時候,咱們很容易陷入過分優化的誤區。如今的設備性能愈來愈好,咱們能夠在主線程中作愈來愈多的事情。

若是某個函數或者方法只有主線程去訪問,那它必然是多線程安全的,由於只有單線程訪問,不存在多線程的狀況。

咱們知道NSMutableArrayNSMutableDictionary這種的是非線程安全的類,在個人使用過程當中,我通常不會對這些東西加鎖,由於我基本只用主線程去訪問,而若是涉及到多線程的話,我會使用不可變的數組和字典。

在大多數狀況下,使用多線程只存在於某一個部分,好比網絡等,那麼在多線程執行完成以後,必定要交由主線程回調。好比,咱們經常使用的AFNetworking中,在回調successfailure的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消失以後,頁面並無跳到主頁。有時候須要過很久纔會跳到主頁,可是頁面並無卡死,手勢解鎖依舊可用。這就奇了怪了,我找了一圈才發現,這個方法是在子線程上回調回來的,而我並不知道。因此我用這個子線程去初始化頁面的時候,就會出現長時間無響應的問題。

因此,系統異步回調的接口必定要去檢查一下是否是主線程的。

GCD 仍是 NSOperationQueue

咱們知道,在 iOS 4 以上,NSOperationQueue是在GCD上封裝上來的,相比起GCD,NSOperationQueue具備以下一些優勢:

  • 提供cancel操做。

  • 更細粒度的優先級控制。

  • 支持繼承,方便封裝。

  • 支持KVO。

而GCD相比起NSOperationQueue的優勢是:

  • 使用方便、簡單。

  • 速度可能更快一點。

我相信,對於大部分好的封裝來講,會優先選擇NSOperationQueue。而若是你只是一個很小的項目,以使用方便爲主,那麼,使用GCD也是一種不錯的選擇。

相關文章
相關標籤/搜索