iOS 編寫高質量Objective-C程式碼(七)

QiShare發表於2018-10-29

級別: ★★☆☆☆
標籤:「iOS」「GCD」「Objective-C」
作者: MrLiuQ
審校: QiShare團隊

前言:
這幾篇文章是小編在鑽研《Effective Objective-C 2.0》的知識產出,其中包含作者和小編的觀點,以及小編整理的一些demo。希望能幫助大家以簡潔的文字快速領悟原作者的精華。
在這裡,QiShare團隊向原作者Matt Galloway表達誠摯的敬意。

文章目錄如下:
iOS 編寫高質量Objective-C程式碼(一)
iOS 編寫高質量Objective-C程式碼(二)
iOS 編寫高質量Objective-C程式碼(三)
iOS 編寫高質量Objective-C程式碼(四)
iOS 編寫高質量Objective-C程式碼(五)
iOS 編寫高質量Objective-C程式碼(六)
iOS 編寫高質量Objective-C程式碼(七)
iOS 編寫高質量Objective-C程式碼(八)


本篇的主題是iOS中的 “ 大中樞開發 GCD ”

先簡單介紹一下今天的主角:GCD

  • GCD(Grand Central Dispatch):一種與塊相關的技術,提供了對執行緒的抽象管理(基於派發佇列dispatch queue)。GCD會根據系統資源情況,適時且高效地 “建立執行緒” 、“複用執行緒” 、 “銷燬執行緒”

一、多用派發佇列,少用同步鎖

問:在iOS開發中,如何通過鎖來提供同步機制?(以前面試中,經常問道的問題..)

答:在GCD出現之前,有兩種方式:

  • 同步塊:@synchronized(self) {...}
- (void)synchronizedMethod {
    
    @synchronized (self) {
        
        // Safe area...
    }
}
複製程式碼
  • NSLock:[_lock lock]; & [_lock unlock];
_lock = [[NSLock alloc] init];

- (void)synchronizedMethod {
    
    [_lock lock];    
    // Safe area..    
    [_lock unlock];
}
複製程式碼

不過這兩種寫法效率很低,如果有很多屬性,那麼每個屬性的同步塊都要等其他同步塊執行完畢才能執行。

GCD出現後,GCD與Block相結合,使開發變得更加簡單、高效。

問:如何保證屬性讀寫時執行緒絕對安全? 答:在屬性寫入時,使用柵欄塊barrier。只有當前所有併發塊都執行完畢後,才會執行barrier塊,然後才會繼續向下處理。

  • 思路如下:

iOS 編寫高質量Objective-C程式碼(七)

  • 程式碼如下:
_syncQueue = dispatch_queue_create("syncQueue", DISPATCH_QUEUE_CONCURRENT);

//! 讀取字串
- (NSString *)someString {

    __block NSString *localSomeString;

    dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });

    return localSomeString;
}

- (void)setSomeString:(NSString *)someString {

     dispatch_barrier_async(_syncQueue, ^{
        _someString = someString;
    });
}
複製程式碼

二、多用GCD,少用performSelector系列方法

performSelector系列方法的缺點有兩個:

  1. performSelector系列方法可能引起記憶體洩漏: 在ARC環境下,編譯器並不知道將要呼叫的選擇子是什麼,有沒有返回值,返回值是什麼,所以ARC不能判斷返回值是否能釋放,因此ARC做了一個比較謹慎的做法:只新增retain,不新增release。因此在有返回值或引數的時候可能導致記憶體洩漏。
  2. performSelector系列方法的返回值只能是void或OC物件型別。
  3. performSelector系列方法最多隻能傳入兩個引數。

因此可以使用GCD來代替performSelector系列方法:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
    //do something..
});
複製程式碼

三、掌握GCD及操作佇列的使用時機

GCD效能很棒,但在執行後臺任務時,GCD並不一定是最佳選擇。在iOS開發中,還有一種技術叫NSOperationQueueGCD是基於C語言的API,效能較高。而NSOperationQueue是基於GCD的抽象。

使用NSOperationNSOperationQueue的優點:

  • 支援取消某個NSOperation: 在執行任務前,可以在NSOperation物件上呼叫cancel方法,用以表明此任務不需要執行。不過已經啟動的任務無法取消。iOS 8之前,GCD佇列是無法取消的,GCD是“安排好之後就不管了(fire and forget)”。iOS 8之後,支援dispatch_canceldispatch_block_cancel

  • NSOperation支援多工操作的依賴關係: 比如:任務A、B、C必須在任務D完成後執行。

  • 支援通過KVO監控NSOperation物件的屬性: 例如:可以通過isCancelled屬性來判斷任務是否已取消,通過isFinished屬性來判斷任務是否已經完成等等;

  • 支援指定NSOperationQueue的優先順序: 操作的優先順序表示此操作與佇列中其他操作之間的優先關係,優先順序高的NSOperationQueue先執行,優先順序低的後執行。GCD的佇列也有優先順序,不過不是針對整個佇列的;

  • 重用NSOperation物件: 在開發中你可以使用NSOperation的子類或者自己建立NSOperation物件來儲存一些資訊,可以在類中定義方法,使得程式碼能夠多次使用;

四、通過Dispatch Group機制,根據系統資源狀況來執行任務

dispatch groupGCD的一項特性,能夠把任務進行分組管理,然後等待這組任務執行完畢時會有通知,開發者可以拿到結果然後繼續下一步操作。 另外,通過dispatch group在併發佇列上同時執行多項任務的時候,GCD會根據系統資源狀態來幫忙排程這些併發執行的任務。

五、使用dispatch_once來執行只需要執行一次的執行緒安全程式碼

例如:我們開發中寫一個單例,就可以使用dispatch_once

+ (instancetype)sharedInstance {
    
    static Class *manager = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[Class alloc] init];
    });

    return manager;
}
複製程式碼

六、不要使用dispatch_get_current_queue

理由如下:

  • dispatch_get_current_queue 函式的行為常常與開發者所預期的不同,此函式已經廢棄,只應做除錯之用。
  • 由於GCD是按層級來組織的,所以無法單用某個佇列物件來描述"當前佇列"這一概念。
  • dispatch_get_current_queue 函式用於解決由不可以重入的程式碼所引發的死鎖,然後能用此函式解決的問題,通常也可以用"佇列特定資料"來解決。

最後,特別緻謝:《Effective Objective-C 2.0》第六章。


關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)


推薦文章:
奇舞週刊
iOS 繪製漸變·基礎篇
iOS 繪製漸變·例項篇

相關文章