小時光上傳隊列分享——NSOperation的使用

上傳隊列分享——NSOperation的使用

1. 需求分析

發一條記錄的流程(而且能夠同時發多條記錄)html

img1

其餘產品/技術需求(影響到設計因此也加在這)面試

  1. 併發要求——圖片確定要支持併發上傳、記錄串行上傳(並行也能夠,可是圖片已經支持併發了,因此沒意義,除非每條記錄都是一張照片)。
  2. 任務可取消——用戶能夠暫停、繼續、刪除一條任務(影響技術選型)。
  3. 支持殺死app後,啓動app繼續任務。

2. 設計(上傳隊列)

無論你在何處工做,構建些什麼,用何種編程語言,在軟件開發上,一直伴隨你的那個不變真理是什麼?—— 《Head First 設計模式》編程

A. 年輕時的想法(針對實現設計)

維護兩種隊列,暫且稱爲一級隊列和二級隊列。數據模型的設計和看到的界面(業務)是對應的,思路比較天然。如今我稱之爲針對實現設計。優缺點咱們後面陳述。設計模式

img2

B. 稍後來的想法(面向抽象設計/分層)

主要分爲兩層:markdown

  • 上傳層(只負責上傳圖片,有上傳單張圖片的任務扔過來就好)
  • 日記隊列層(面向業務)

img3

二者最主要的區別:多線程

計A每一處設計都是面向業務的,沒有抽象;設計B中抽象層是把「上傳圖片功能」抽象了出來,能夠設計的徹底不知道業務(什麼叫不知道業務?)併發

B的設計有什麼優勢?app

  • 易擴充——因爲是抽象出來的通用模塊,App中其餘的上傳圖片業務(好比更改頭像、支持H5上傳一張圖片)均可以不改代碼直接使用,甚至換成別的App也能夠直接拿過來使用。
  • 易維護——假如上傳圖片不使用七牛了,換成了騰訊雲,只須要更改上傳層的代碼就行了,否則的話,可能會改不少地方。

上傳層其實還能夠抽象出不少層異步

3. 技術實現

Why NSOperation?

最關鍵是實現上傳隊列,第一反應確定是考慮GCD或者NSOperation。這裏說下最終選擇NSOperation的緣由:async

  1. NSOperation自然的OOP,咱們只須要將單個上傳的邏輯封裝在一個NSOperation便可,而使用GCD,須要額外不少代碼來封裝。
  2. 須要支持cancel一個任務,因此選擇NSOperation會方便些。

NSOperation類介紹

對於NSOperation,須要瞭解如下幾件事:

  1. NSOperation爲抽象類,咱們直接使用NSOperation,通常都是自定義NSOperation的子類,或者使用系統提供的兩個子類,NSInvocationOperation和NSBlockOperation。(後兩個其實都不怎麼經常使用,本身查資料瞭解)。
  2. 兩種執行NSOperation的方式:扔到NSOperation Queue裏或者調用「start」方法手動觸發。
  3. Dependencies,使用NSOperation能夠方便的設置任務之間的依賴關係(面試總愛問,我們沒有用,其實有個場景想一下以爲很合適?)
  4. 若是手動調用start執行一個任務,那麼默認任務將會在調用start的線程,同步執行。(isAsynchronous屬性)
  5. When you add an operation to an operation queue, the queue ignores the value of the asynchronous property and always calls the start method from a separate thread.

自定義NSOperation子類(今天講的重點)

1. 使用好處

  1. 自然分離代碼到一個類中,不容易形成代碼臃腫
  2. 對於子線程裏作異步任務,可以比較靈活的處理(每一個NSOperation裏執行的依然是一個異步任務、token、七牛上傳),換句話講,可以本身來控制每一個NSOperation的狀態。

2. 典型作法

先回憶一下,我們要幹什麼 —— 用一個隊列維護一個或者多個任務,每一個任務就是上傳一個圖片到七牛

先來解決「每一個任務就是上傳一個圖片到七牛」這件事,會想到3點要面臨的挑戰:

  1. 上傳這個耗時的任務,放哪,怎麼處理。
  2. 怎麼得知任務已經完成。
  3. 若是中途取消了怎麼處理。

下面經過代碼來看下使用NSOperation自定義類,怎麼處理這三個問題,代碼來自Apple官方文檔《Concurrency Programming Guide》,能夠對照着看下我們的代碼。

@interface MyOperation : NSOperation {
    BOOL        executing;
    BOOL        finished;
}
- (void)completeOperation;
@end
 
@implementation MyOperation
- (id)init {
    self = [super init];
    if (self) {
        executing = NO;
        finished = NO;
    }
    return self;
}
 
- (BOOL)isConcurrent { // ------------2
    return YES;
}
 
- (BOOL)isExecuting { // ------------2
    return executing;
}
 
- (BOOL)isFinished { // ------------2
    return finished;
}

- (void)start { // ------------1
   // Always check for cancellation before launching the task.
   if ([self isCancelled]) // ------------3
   {
      // Must move the operation to the finished state if it is canceled.
      [self willChangeValueForKey:@"isFinished"];
      finished = YES;
      [self didChangeValueForKey:@"isFinished"];
      return;
   }
 
   // If the operation is not canceled, begin executing the task.
   [self willChangeValueForKey:@"isExecuting"];
   [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
   executing = YES;
   [self didChangeValueForKey:@"isExecuting"];
}

- (void)main { // ------------1
   @try {
 
       // Do the main work of the operation here.
 
       [self completeOperation];
   }
   @catch(...) {
      // Do not rethrow exceptions.
   }
}
 
- (void)completeOperation {
    [self willChangeValueForKey:@"isFinished"]; // ------------4
    [self willChangeValueForKey:@"isExecuting"];
 
    executing = NO;
    finished = YES;
 
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}
@end

代碼解讀

  1. 前面已經說過NSOperation是抽象類了,因此子類確定要實現它的抽象方法,通常須要實現start、main方法。start方法裏通常,更改狀態,main用來執行主要任務(上傳圖片放這裏!!
  2. 覆寫幾個狀態的getter方法。指定用來記錄狀態的變量,好比executing、finished,因此係統能夠根據NSOperation的狀態,進行相應的處理。
  3. 階段性檢查任務是否被cancel,被cancel以後要把executing、finished改成對應的狀態值。(這一步很重要)。
  4. 手動觸發KVO,告訴系統任務的狀態發生了改變。而後系統調用getter方法時,發現isFinished狀態變成了YES,則認爲這個任務完成,移除NSOperation Queue。

剩餘一些細節

  1. 上傳七牛以前,須要先經過我們本身API獲取token。若是不能本身靈活控制NSOperation的狀態,不少步驟都要作出成同步的(自定義NSOperation給咱們更大的靈活性,不管這個任務有多複雜,是同步仍是異步,均可以控制它狀態的變化)
  2. 咱們知道,每一個Operation的main方法,確定是會併發運行的,而token的獲取其實只要獲取一次,就行了,因此,咱們使用了信號量dispatch_semaphore來確保只之執行一次token的請求。

  3. 而後,每張圖片上傳以前,會使用系統方法,作一次人臉識別、寫入一次Exif信息,這兩部都是很是佔用內存的。若是併發執行,頗有可能讓內容衝到必定高度而Out Of Memory,爲了不這個問題,一個是人臉識別只使用一張小圖進行識別(不超過640*640),而且對於這兩個過程,加鎖。(關於iOS裏幾種鎖的用法和優缺點,建議瞭解一下,面試特別愛問)

3. 將任務放到Queue中

如今咱們已經知道一個任務如何實現了,只須要將NSOperation扔到NSOperation Queue中,就會自動執行了。併發數可使用NSOperation Queue的maxConcurrentOperationCount來控制併發數。

考慮一個問題:何時往Queue裏添加NSOperation?(一次性全加入?仍是?)

4. 多線程其餘知識

異步任務的同步處理幾種方法

  1. NSOperation自定義子類
  2. 使用GCD group的時候,能夠對其中異步任務使用dispatch_group_enter和dispatch_group_leave
  3. GCD信號量——dispatch_semaphore ['seməfɔ:]

手動觸發KVO

- (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies {
    [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
    _HTTPShouldHandleCookies = HTTPShouldHandleCookies;
    [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
}

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
        return NO;
    }

    return [super automaticallyNotifiesObserversForKey:key];
}

5. 推薦工具

  1. SpaceLauncher
  2. Magnet
  3. MWeb(強烈推薦,幾大功能:預覽主題、一鍵把網頁變成Markdown、導出、側邊欄)
  4. 小技巧——寫markdown的時候,怎麼方便的對齊代碼

6. 參考文檔

  1. 蘋果文檔NSOperation
  2. https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationObjects/OperationObjects.html#//apple_ref/doc/uid/TP40008091-CH101-SW1
相關文章
相關標籤/搜索