ios多線程編程知識小集

iOS爲開發者提供了豐富的多線程編程機制,從最直接最簡單的調用NSObjectperformSelector系列API,到NSThreadOperation Queues以及Dispatch Queues,當然iOS也支持更原始的pthread。本文主要討論使用這些多線程編程機制時候的一些注意事項,具體如何使用這些機制,可以移步到Raywenderlich的教程以及官方文檔:

  1. Grand Central Dispatch In-Depth: Part 1/2
  2. Grand Central Dispatch In-Depth: Part 2/2
  3. How To Use NSOperations and NSOperationQueues
  4. Concurrency Programming Guide

前兩篇教程通過逐步改善一個圖片集加載的例子,來說明GCD(Grant Central Dispatch)在數據讀寫同步、數據加載以及線程同步上的應用;類似的,第三篇教程通過一個加載table內容的例子,說明了NSOperation以及NSOperationQueue的應用。

NSOperation和NSOperationQueue

相比其他iOS多線程機制,NSOperation給開發者提供了更多獨特的特性,這些特性包括:

  1. NSOperation將任務封裝在對象中,完全支持OOP開發方式,有利於保持代碼結構的一致性。

  2. NSOperation對象特性使得可以給任務線程設置相應的屬性,比如NSOperation爲開發者定義了三個屬性isExecutingNSOperationisCanceled,當然開發者在子類化NSOperation時,可以根據需求定義額外的屬性;之後開發者可以對這些屬性進行KVO。

  3. 調用NSOperationcancel方法可以重置任務線程的isCanceled屬性,任務根據這個狀態作出相應的操作,比如退出或者暫停等;調用NSOperationQueuecancelAllOperations方法可以重置該隊列上所有任務的isCanceled屬性;

  4. 任務隊列支持暫停操作,調用NSOperationQueuesetSuspended:(BOOL)可以暫停或者重啓隊列

  5. 任務之間支持互相依賴,通過調用NSOperationaddDependency:(NSOperation *)方法爲自身任務添加依賴任務線程,調用removeDependency:(NSOperation *)可以將依賴隊列移除

  6. 可以給任務隊列中的任務設置運行優先級,從而控制任務隊列中任務執行順序

  7. 可以給任務設置一個completionBlock,該block在任務運行結束後執行。

下面總結幾點在使用隊列和任務時的幾個要點:

1)NSOperation是一個抽象類,開發者需要繼承該類並至少要重載main方法,

@interface CustomizedOperation : NSOperation
@property(strong) id  myData;
-(id)initWithData:(id)data;
@end

@implementation CustomizedOperation
- (id)initWithData:(id)data {
  if (self = [super init]){
    _myData = data;
  }
  return self;
}

- (void)main {
  @autoreleasepool {
    while (true) {
      // TODOs:
    }
  }
}
@end

2) 前面提到,可以調用cancel或者cancelAllOperations方法,但是這兩個方法除了重置任務的isCanceled屬性之外,不會做其他操作,要想達到真正取消任務的目的,需要開發者自己不斷檢查給屬性的狀態,並作出相應操作,如:

@interface CustomOperation: NSOperation
@end

@implementation CustomOperation
- (void)main {
  // 耗時操作
  @autoreleasepool {
    for (int i = 0 ; i < 1000000 ; i++) {

      // 檢查任務狀態
      if (self.isCancelled)
        break;

      // step 1
      [self doSomething];

      // 檢查任務狀態
      if (self.isCancelled)
        break;

      // step 2
      [self doSomethingelse];

    }
  }
}
@end

3) 任務默認都是同步執行的,如果想要任務異步執行,開發者需要自己維護任務的狀態,需要重寫start, isExecuting, isFinished, isConcurrent方法。其中重載start方法時不需要調用父類方法,isConcurrent方法只需要直接返回YES即可。

@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;
}

- (void)start {
  // 啓動任務前檢查任務是否被取消
  if ([self isCancelled])
  {
    // 如果任務被取消,則重置其他屬性狀態.
    [self willChangeValueForKey:@"isFinished"];
    _finished = YES;
    [self didChangeValueForKey:@"isFinished"];
    return;
  }
  [self willChangeValueForKey:@"isExecuting"];
  [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
  _executing = YES;
  [self didChangeValueForKey:@"isExecuting"];
}

- (void)main {
  // 這裏執行主要任務

  // 檢查任務狀態
  if (self.isCancelled)
    break;

  // step 1
  [self doSomething];

  // 檢查任務狀態
  if (self.isCancelled)
    break;

  // step 2
  [self doSomethingelse];

  [self completeOperation];
}

- (void)completeOperation {
  [self willChangeValueForKey:@"isFinished"];
  [self willChangeValueForKey:@"isExecuting"];
  _executing = NO;
  _finished = YES;
  [self didChangeValueForKey:@"isExecuting"];
  [self didChangeValueForKey:@"isFinished"];
}

- (BOOL)isConcurrent {
  return YES;
}

- (BOOL)isExecuting {
  return _executing;
}

- (BOOL)isFinished {
  return _finished;
}

GCD(Grant Central Dispatch)

1)dispatch queue的創建時候需要傳遞一個串行或者並行標誌,GCD庫提供了這兩個標誌,但是很多開發者卻不使用,下面是正確的姿勢:

dispatch_queue_t serialQueue = dispatch_queue_create("com.johnkui.serial", **DISPATCH_QUEUE_SERIAL**);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.johnkui.concurrent", **DISPATCH_QUEUE_CONCURRENT**);

2)我們知道使用dispatch_group_async或者dispatch_group_enter/dispatch_group_leave配合dispatch_group_notify或者dispatch_group_wait可以實現同步操作,但是如果使用這些API不當的話,結果卻會出人意料。下面是使用dispatch_group_async的正確姿勢:

dispatch_queue_t queueA = dispatch_queue_create("com.johnkui.concurrentA", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queueB = dispatch_queue_create("com.johnkui.concurrentB", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();

// 給任務組添加任務
dispatch_group_async(group, queueA, ^{
  // Some asynchronous work
});

// 給任務組添加其他任務
dispatch_group_async(group, queueB, ^{
  // Some other asynchronous work
});

// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

dispatch_group_async配合dispatch_group_wait使用時,要保證dispatch_group_async調用的block必須運行相應的隊列中,在上例中分爲爲queueA和queueB中,如果block裏的任務實際運行在其他queue中,則dispatch_group_wait將立即返回而監測不到兩個任務的完成狀態,因爲group只監控和該group關聯的queue上的任務的運行狀態。如果block裏面的任務確實想運行在其他queue上,則可以使用dispatch_group_enter/dispatch_group_leave,下面是正確的姿勢:

  dispatch_group_t group = dispatch_group_create();
  dispatch_queue_t queueA = dispatch_queue_create("com.johnkui.concurrentA", DISPATCH_QUEUE_CONCURRENT);
  dispatch_queue_t queueB = dispatch_queue_create("com.johnkui.concurrentB", DISPATCH_QUEUE_CONCURRENT);

  dispatch_group_enter(group);
  dispatch_async(queueA, ^{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      // 執行耗時任務

      dispatch_group_leave(group);
    });
  });

  dispatch_group_enter(group);
  dispatch_async(queueB, ^{
    dispatch_async(dispatch_get_main_queue(), ^{
      UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Hello"
                                                          message:@"任務完成了!"
                                                         delegate:nil
                                                cancelButtonTitle:@"Ok"
                                                otherButtonTitles: nil];
      [alertView show];
      dispatch_group_leave(group);
    });
  });


  dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"group is finished...");
  });

當block裏的任務運行完需要更新UI時,一般都會這麼操作:

    dispatch_async(dispatch_get_main_queue(), ^{
        //更新UI
    });

這個時候一定不要使用同步的dispatch_group_wait(group, DISPATCH_TIME_FOREVER),否則整個主線程將出現死鎖,而是使用異步的dispatch_group_notify;

發佈了40 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章