iOS--多線程具體總結(OC&Swift)

整理一下 iOS 開發中幾種多線程方案,以及其使用方法和注意事項。當然也會給出幾種多線程的案例,在實際使用中感受它們的區別。使用 Swift 和 Objective-C 兩種語言講解

在 iOS 中其實目前有 4 套多線程方案,他們分別是:

1,Pthreads
2,NSThread
3,GCD
4,NSOperation & NSOperationQueue

Pthreads

POSIX線程(POSIX threads),簡稱Pthreads,是線程的POSIX標準。該標準定義了創建和操縱線程的一整套API。在類Unix操作系統(Unix、Linux、Mac OS X等)中,都使用Pthreads作爲操作系統的線程。
簡單地說,這是一套在很多操作系統上都通用的多線程API,所以移植性很強,當然在 iOS 中也是可以的。

Objective-C
包含頭文件

#import <pthread.h>
然後創建線程,並執行任務


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    pthread_t thread;
    //創建一個線程並自動執行
    pthread_create(&thread, NULL, start, NULL);
}

void *start(void *data) {
    NSLog(@"%@", [NSThread currentThread]);

    return NULL;
}

打印輸出:

{number = 2, name = (null)}
看代碼就會發現他需要 c語言函數,你需要手動處理線程的各個狀態的轉換即管理生命週期,這段代碼雖然創建了一個線程,但並沒有銷燬。

NSThread
這套方案是經過蘋果封裝後的,並且完全面向對象的。所以你可以直接操控線程對象,非常直觀和方便。但是,它的生命週期還是需要我們手動管理,所以這套方案也是偶爾用用,比如 [NSThread currentThread],它可以獲取當前線程類,你就可以知道當前線程的各種屬性,用於調試十分方便。下面來看看它的一些用法。

創建並啓動
先創建線程類,再啓動

Objective-C

  // 創建
  NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];

  // 啓動
  [thread start];

Swift

  //創建
  let thread = NSThread(target: self, selector: "run:", object: nil)

  //啓動
  thread.start()

創建並自動啓動

Objective-C

  [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];

Swift

  NSThread.detachNewThreadSelector("run:", toTarget: self, withObject: nil)

使用 NSObject 的方法創建並自動啓動

Objective-C

  [self performSelectorInBackground:@selector(run:) withObject:nil];

Swift
蘋果認爲 performSelector: 不安全,所以在 Swift 去掉了這個方法。
Note: The performSelector: method and related selector-invoking methods are not imported in Swift because they are inherently unsafe.
其他方法
除了創建啓動外,NSThread 還以很多方法,下面我列舉一些常見的方法,當然我列舉的並不完整,更多方法大家可以去類的定義裏去看。

Objective-C
//取消線程
- (void)cancel;

//啓動線程
- (void)start;

//判斷某個線程的狀態的屬性

@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;

//設置和獲取線程名字

-(void)setName:(NSString *)n;
-(NSString *)name;

//獲取當前線程信息

+ (NSThread *)currentThread;

//獲取主線程信息

+ (NSThread *)mainThread;

//使當前線程暫停一段時間,或者暫停到某個時刻

+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

Swift
Swift的方法名字和OC的方法名都一樣

GCD
Grand Central Dispatch,它是蘋果爲多核的並行運算提出的解決方案,所以會自動合理地利用更多的CPU內核(比如雙核、四核),最重要的是它會自動管理線程的生命週期(創建線程、調度任務、銷燬線程),完全不需要我們管理,我們只需要告訴幹什麼就行。同時它使用的也是 c語言,不過由於使用了 Block(Swift裏叫做閉包),使得使用起來更加方便,而且靈活。

任務和隊列
在 GCD 中,加入了兩個非常重要的概念: 任務 和 隊列。

任務:即操作,你想要幹什麼,說白了就是一段代碼,在 GCD 中就是一個 Block,所以添加任務十分方便。任務有兩種執行方式: 同步執行 和 異步執行,他們之間的區別是 是否會創建新的線程。

同步執行:只要是同步執行的任務,都會在當前線程執行,不會另開線程。

異步執行:只要是異步執行的任務,都會另開線程,在別的線程執行。

這裏說的並不準確,同步(sync) 和 異步(async) 的主要區別在於會不會阻塞當前線程,直到 Block 中的任務執行完畢!
如果是 同步(sync) 操作,它會阻塞當前線程並等待 Block 中的任務執行完畢,然後當前線程纔會繼續往下運行。
如果是 異步(async)操作,當前線程會直接往下執行,它不會阻塞當前線程。
隊列:用於存放任務。一共有兩種隊列, 串行隊列 和 並行隊列。

串行隊列 中的任務會根據隊列的定義 FIFO 的執行,一個接一個的先進先出的進行執行。

放到串行隊列的任務,GCD 會 FIFO(先進先出) 地取出來一個,執行一個,然後取下一個,這樣一個一個的執行。
並行隊列 中的任務 根據同步或異步有不同的執行方式。

放到並行隊列的任務,GCD 也會 FIFO的取出來,但不同的是,它取出來一個就會放到別的線程,然後再取出來一個又放到另一個的線程。這樣由於取的動作很快,忽略不計,看起來,所有的任務都是一起執行的。不過需要注意,GCD 會根據系統資源控制並行的數量,所以如果任務很多,它並不會讓所有任務同時執行。
雖然很繞,但請看下錶:

同步執行 異步執行
串行隊列 當前線程,一個一個執行 其他線程,一個一個執行
並行隊列 當前線程,一個一個執行 開很多線程,一起執行
創建隊列
主隊列:這是一個特殊的 串行隊列。什麼是主隊列,大家都知道吧,它用於刷新 UI,任何需要刷新 UI 的工作都要在主隊列執行,所以一般耗時的任務都要放到別的線程執行。

//Objective-C

  dispatch_queue_t queue = ispatch_get_main_queue();

//Swift

  let queue = ispatch_get_main_queue()

自己創建的隊列:凡是自己創建的隊列都是 串行隊列。 其中第一個參數是標識符,用於 DEBUG 的時候標識唯一的隊列,可以爲空。


第二個參數用來表示創建的隊列是串行的還是並行的,傳入 DISPATCH_QUEUE_SERIAL 或 NULL 表示創建串行隊列。傳入 DISPATCH_QUEUE_CONCURRENT 表示創建並行隊列。
//Objective-C
//串行隊列

  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);

//並行隊列

  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);

//Swift
//串行隊列

  let queue = dispatch_queue_create("tk.bourne.testQueue", nil);
  let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL)

//並行隊列

  let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT)

全局並行隊列:這應該是唯一一個並行隊列, 只要是並行任務一般都加入到這個隊列。這是系統提供的一個併發隊列。

//Objective-C

  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//Swift

  let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

創建任務
同步任務: 不會另開線程 ,會阻塞當前線程 (SYNC)

Objective-C

  dispatch_sync(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });

Swift

  dispatch_sync(<#queue#>, { () -> Void in
      //code here
      println(NSThread.currentThread())
  })

異步任務:會另開線程 不會阻塞當前線程 (ASYNC)

Objective-C

  dispatch_async(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);

Swift

  dispatch_async(<#queue#>, { () -> Void in
      //code here
      println(NSThread.currentThread())
  })

爲了更好的理解同步和異步,和各種隊列的使用,下面看兩個示例:

示例一:
以下代碼在主線程調用,結果是什麼?

NSLog("之前 - %@", NSThread.currentThread())
dispatch_sync(dispatch_get_main_queue(), { () -> Void in 
        NSLog("sync - %@", NSThread.currentThread())
})
NSLog("之後 - %@", NSThread.currentThread())

答案:
只會打印第一句:之前 - {number = 1, name = main} ,然後主線程就卡死了,你可以在界面上放一個按鈕,你就會發現點不了了。
解釋:
同步任務會阻塞當前線程,然後把 Block 中的任務放到指定的隊列中執行,只有等到 Block 中的任務完成後纔會讓當前線程繼續往下運行。
那麼這裏的步驟就是:打印完第一句後,dispatch_sync 立即阻塞當前的主線程,然後把 Block 中的任務放到 main_queue 中,可是 main_queue 中的任務會被取出來放到主線程中執行,但主線程這個時候已經被阻塞了,所以 Block 中的任務就不能完成,它不完成,dispatch_sync 就會一直阻塞主線程,這就是死鎖現象。導致主線程一直卡死。

示例二:
以下代碼會產生什麼結果?

let queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL)

   NSLog("之前 - %@", NSThread.currentThread())

    dispatch_async(queue, { () -> Void in
        NSLog("sync之前 - %@", NSThread.currentThread())
        dispatch_sync(queue, { () -> Void in
             NSLog("sync - %@", NSThread.currentThread())
        })
        NSLog("sync之後 - %@", NSThread.currentThread())
   })

  NSLog("之後 - %@", NSThread.currentThread())

答案:
之前 - {number = 1, name = main}
sync之前 - {number = 2, name = (null)}
之後 - {number = 1, name = main}
很明顯 sync - %@ 和 sync之後 - %@ 沒有被打印出來!這是爲什麼呢?我們再來分析一下:

分析:
我們按執行順序一步步來:

使用 DISPATCH_QUEUE_SERIAL 這個參數,創建了一個 串行隊列。
打印出 之前 - %@ 這句。
dispatch_async 異步執行,所以當前線程不會被阻塞,於是有了兩條線程,一條當前線程繼續往下打印出 之後 - %@這句, 另一臺執行 Block 中的內容打印 sync之前 - %@ 這句。因爲這兩條是並行的,所以打印的先後順序無所謂。
注意,高潮來了。現在的情況和上一個例子一樣了。dispatch_sync同步執行,於是它所在的線程會被阻塞,一直等到 sync 裏的任務執行完纔會繼續往下。於是 sync 就高興的把自己 Block 中的任務放到 queue 中,可誰想 queue 是一個串行隊列,一次執行一個任務,所以 sync 的 Block 必須等到前一個任務執行完畢,可萬萬沒想到的是 queue 正在執行的任務就是被 sync 阻塞了的那個。於是又發生了死鎖。所以 sync 所在的線程被卡死了。剩下的兩句代碼自然不會打印。
隊列組
隊列組可以將很多隊列添加到一個組裏,這樣做的好處是,當這個組裏所有的任務都執行完了,隊列組會通過一個方法通知我們。下面是使用方法,這是一個很實用的功能。

Objective-C

//1.創建隊列組
dispatch_group_t group = dispatch_group_create();

//2.創建隊列

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//3.多次使用隊列組的方法執行任務, 只有異步方法
//3.1.執行3次循環

dispatch_group_async(group, queue, ^{
    for (NSInteger i = 0; i < 3; i++) {
        NSLog(@"group-01 - %@", [NSThread currentThread]);
    }
});

//3.2.主隊列執行8次循環

dispatch_group_async(group, dispatch_get_main_queue(), ^{
    for (NSInteger i = 0; i < 8; i++) {
        NSLog(@"group-02 - %@", [NSThread currentThread]);
    }
});

//3.3.執行5次循環

dispatch_group_async(group, queue, ^{
    for (NSInteger i = 0; i < 5; i++) {
        NSLog(@"group-03 - %@", [NSThread currentThread]);
    }
});

//4.都完成後會自動通知

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"完成 - %@", [NSThread currentThread]);
});

Swift
//1.創建隊列組

let group = dispatch_group_create()

//2.創建隊列

let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

//3.多次使用隊列組的方法執行任務, 只有異步方法
//3.1.執行3次循環

dispatch_group_async(group, queue) { () -> Void in
    for _ in 0..<3 {
        NSLog("group-01 - %@", NSThread.currentThread())
    }
}

//3.2.主隊列執行8次循環

dispatch_group_async(group, dispatch_get_main_queue()) { () -> Void in
    for _ in 0..<8 {
        NSLog("group-02 - %@", NSThread.currentThread())
    }
}

//3.3.執行5次循環

dispatch_group_async(group, queue) { () -> Void in
    for _ in 0..<5 {
        NSLog("group-03 - %@", NSThread.currentThread())
    }
}

//4.都完成後會自動通知

dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
    NSLog("完成 - %@", NSThread.currentThread())
}

打印結果

group-03 - {number = 3, name = (null)}
group-02 - {number = 1, name = main}
group-02 - {number = 1, name = main}
group-03 - {number = 3, name = (null)}
group-02 - {number = 1, name = main}
group-03 - {number = 3, name = (null)}
group-03 - {number = 3, name = (null)}
group-02 - {number = 1, name = main}
group-01 - {number = 2, name = (null)}
group-03 - {number = 3, name = (null)}
group-02 - {number = 1, name = main}
group-01 - {number = 2, name = (null)}
group-02 - {number = 1, name = main}
group-01 - {number = 2, name = (null)}
group-02 - {number = 1, name = main}
group-02 - {number = 1, name = main}
完成 - {number = 1, name = main}
這些就是 GCD 的基本功能

關於GCD,還有兩個需要說的:

func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

這個方法重點是你傳入的 queue,當你傳入的 queue 是通過 DISPATCH_QUEUE_CONCURRENT 參數自己創建的 queue 時,這個方法會阻塞這個 queue(注意是阻塞 queue ,而不是阻塞當前線程),一直等到這個 queue 中排在它前面的任務都執行完成後纔會開始執行自己,自己執行完畢後,再會取消阻塞,使這個 queue 中排在它後面的任務繼續執行。
如果你傳入的是其他的 queue, 那麼它就和 dispatch_async 一樣了。

func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

這個方法的使用和上一個一樣,傳入 自定義的併發隊列(DISPATCH_QUEUE_CONCURRENT),它和上一個方法一樣的阻塞 queue,不同的是 這個方法還會 阻塞當前線程。
如果你傳入的是其他的 queue, 那麼它就和 dispatch_sync 一樣了。

NSOperation和NSOperationQueue
NSOperation 是蘋果公司對 GCD 的封裝,完全面向對象,所以使用起來更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分別對應 GCD 的 任務 和 隊列 。操作步驟也很好理解:

將要執行的任務封裝到一個 NSOperation 對象中。
將此任務添加到一個 NSOperationQueue 對象中。
然後系統就會自動在執行任務。

添加任務
值得說明的是,NSOperation 只是一個抽象類,所以不能封裝任務。但它有 2 個子類用於封裝任務。分別是:NSInvocationOperation 和 NSBlockOperation 。創建一個 Operation 後,需要調用 start 方法來啓動任務,它會 默認在當前隊列同步執行。當然你也可以在中途取消一個任務,只需要調用其 cancel 方法即可。

NSInvocationOperation : 需要傳入一個方法名。

Objective-C

//1.創建NSInvocationOperation對象

  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

//2.開始執行

  [operation start];

Swift
在 Swift 是容不下 NSInvocationOperation 這種不是類型安全的

NSBlockOperation

Objective-C

  //1.創建NSBlockOperation對象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];

//2.開始任務

  [operation start];

Swift

  //1.創建NSBlockOperation對象
  let operation = NSBlockOperation { () -> Void in
      println(NSThread.currentThread())
  }

//2.開始任務

  operation.start()

之前說過這樣的任務,默認會在當前線程執行。但是 NSBlockOperation 還有一個方法:addExecutionBlock: ,通過這個方法可以給 Operation 添加多個執行 Block。這樣 Operation 中的任務 會併發執行,它會 在主線程和其它的多個線程 執行這些任務,注意下面的打印結果:

Objective-C
//1.創建NSBlockOperation對象

      NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
          NSLog(@"%@", [NSThread currentThread]);
      }];

//添加多個Block

      for (NSInteger i = 0; i < 5; i++) {
          [operation addExecutionBlock:^{
              NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
          }];
      }

//2.開始任務

      [operation start];

Swift
//1.創建NSBlockOperation對象

        let operation = NSBlockOperation { () -> Void in
            NSLog("%@", NSThread.currentThread())
        }

//2.添加多個Block

        for i in 0..<5 {
            operation.addExecutionBlock { () -> Void in
                NSLog("第%ld次 - %@", i, NSThread.currentThread())
            }
        }

//2.開始任務

        operation.start()

打印輸出
第2次 - {number = 1, name = main}

第1次 - {number = 4, name = (null)}

{number = 3, name = (null)}

第0次 - {number = 2, name = (null)}

第3次 - {number = 4, name = (null)}

第4次 - {number = 1, name = main}
​注意:
addExecutionBlock 方法必須在 start() 方法之前執行,否則就會報錯:

* -[NSBlockOperation addExecutionBlock:]: blocks cannot be added after the operation has started executing or finished’

自定義Operation

除了上面的兩種 Operation 以外,我們還可以自定義 Operation。自定義 Operation 需要繼承 NSOperation 類,並實現其 main() 方法,因爲在調用 start() 方法的時候,內部會調用 main() 方法完成相關邏輯。所以如果以上的兩個類無法滿足你的慾望的時候,你就需要自定義了。你想要實現什麼功能都可以寫在裏面。除此之外,你還需要實現 cancel() 在內的各種方法。
創建隊列
看過上面的內容就知道,我們可以調用一個 NSOperation 對象的 start() 方法來啓動這個任務,但是這樣做他們默認是 同步執行 的。就算是 addExecutionBlock 方法,也會在 當前線程和其他線程 中執行,也就是說還是會佔用當前線程。這是就要用到隊列 NSOperationQueue 了。而且,按類型來說的話一共有兩種類型:主隊列、其他隊列。只要添加到隊列,會自動調用任務的 start() 方法

主隊列

每套多線程方案都會有一個主線程。這是一個特殊的線程,必須串行。所以添加到主隊列的任務都會一個接一個地排着隊在主線程處理。

//Objective-C

NSOperationQueue *queue = [NSOperationQueue mainQueue];

//Swift

let queue = NSOperationQueue.mainQueue()

其他隊列

因爲主隊列比較特殊,所以會單獨有一個類方法來獲得主隊列。那麼通過初始化產生的隊列就是其他隊列了,因爲只有這兩種隊列,除了主隊列,其他隊列就不需要名字了。

注意:其他隊列的任務會在其他線程並行執行。

Objective-C

//1.創建一個其他隊列    
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

//2.創建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"%@", [NSThread currentThread]);
}];

//3.添加多個Block
for (NSInteger i = 0; i < 5; i++) {
    [operation addExecutionBlock:^{
        NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
    }];
}

//4.隊列添加任務
[queue addOperation:operation];

Swift

//1.創建其他隊列
let queue = NSOperationQueue()

//2.創建NSBlockOperation對象
let operation = NSBlockOperation { () -> Void in
    NSLog("%@", NSThread.currentThread())
}

//3.添加多個Block
for i in 0..<5 {
    operation.addExecutionBlock { () -> Void in
        NSLog("第%ld次 - %@", i, NSThread.currentThread())
    }
}

//4.隊列添加任務
queue.addOperation(operation)
打印輸出

{number = 5, name = (null)}
第2次 - {number = 2, name = (null)}
第0次 - {number = 4, name = (null)}
第1次 - {number = 3, name = (null)}
第3次 - {number = 5, name = (null)}
第4次 - {number = 2, name = (null)}

你不用管串行、並行、同步、異步這些名詞。NSOperationQueue 有一個參數 maxConcurrentOperationCount 最大併發數,用來設置最多可以讓多少個任務同時執行。當你把它設置爲 1 的時候,就是串行.

NSOperationQueue 還有一個添加任務的方法,

- (void)addOperationWithBlock:(void (^)(void))block; 

NSOperation 有一個非常實用的功能,那就是添加依賴。
比如有 3 個任務:
A: 從服務器上下載一張圖片,
B:給這張圖片修改,
C:把圖片返回給服務器。這時就可以用到依賴了:

Objective-C

//1.任務一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下載圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//2.任務二:修改
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"修改   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//3.任務三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上傳圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//4.設置依賴
[operation2 addDependency:operation1];      //任務二依賴任務一
[operation3 addDependency:operation2];      //任務三依賴任務二

//5.創建隊列並加入任務
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

Swift

//1.任務一:下載圖片
let operation1 = NSBlockOperation { () -> Void in
    NSLog("下載圖片 - %@", NSThread.currentThread())
    NSThread.sleepForTimeInterval(1.0)
}

//2.任務二:修改
let operation2 = NSBlockOperation { () -> Void in
    NSLog("修改   - %@", NSThread.currentThread())
    NSThread.sleepForTimeInterval(1.0)
}

//3.任務三:上傳圖片
let operation3 = NSBlockOperation { () -> Void in
    NSLog("上傳圖片 - %@", NSThread.currentThread())
    NSThread.sleepForTimeInterval(1.0)
}

//4.設置依賴
operation2.addDependency(operation1)    //任務二依賴任務一
operation3.addDependency(operation2)    //任務三依賴任務二

//5.創建隊列並加入任務
let queue = NSOperationQueue()
queue.addOperations([operation3, operation2, operation1], waitUntilFinished: false)

打印結果
{number = 2, name = (null)}
修改 - {number = 3, name = (null)}
上傳圖片 - {number = 3, name = (null)}
注意:不能添加相互依賴,會死鎖,比如 A依賴B,B依賴A。
可以使用 removeDependency 來解除依賴關係。
可以在不同的隊列之間依賴,反正就是這個依賴是添加到任務身上的,和隊列沒關係。
其他方法
以上就是一些主要方法, 下面還有一些常用方法需要大家注意:

NSOperation

BOOL executing; //判斷任務是否正在執行

BOOL finished; //判斷任務是否完成

void (^completionBlock)(void); //用來設置完成後需要執行的操作

  • (void)cancel; //取消任務

  • (void)waitUntilFinished; //阻塞當前線程直到此任務執行完畢
    NSOperationQueue

NSUInteger operationCount; //獲取隊列的任務數

  • (void)cancelAllOperations; //取消隊列中所有的任務

  • (void)waitUntilAllOperationsAreFinished; //阻塞當前線程直到此隊列中的所有任務執行完畢

[queue setSuspended:YES]; // 暫停queue

[queue setSuspended:NO]; // 繼續queue
好啦,到這裏差不多就講完了。當然,我講的並不完整,可能有一些知識我並沒有講到,但作爲常用方法,這些已經足夠了。不過我在這裏只是告訴你了一些方法的功能,只是怎麼把他們用到合適的地方,就需要多多實踐了。下面我會說一些關於多線程的案例,是大家更加什麼地瞭解。

其他用法
在這部分,我會說一些和多線程知識相關的案例,可能有些很簡單,大家早都知道的,不過因爲這篇文章講的是多線程嘛,所以應該儘可能的全面嘛。還有就是,我會儘可能的使用多種方法實現,讓大家看看其中的區別。

線程同步
所謂線程同步就是爲了防止多個線程搶奪同一個資源造成的數據安全問題,所採取的一種措施。當然也有很多實現方法,請往下看:

互斥鎖 :給需要同步的代碼塊加一個互斥鎖,就可以保證每次只有一個線程訪問此代碼塊。

Objective-C

@synchronized(self) {
    //需要執行的代碼塊
}

Swift

objc_sync_enter(self)

//需要執行的代碼塊

objc_sync_exit(self)

同步執行 :我們可以使用多線程的知識,把多個線程都要執行此段代碼添加到同一個串行隊列,這樣就實現了線程同步的概念。當然這裏可以使用 GCD 和 NSOperation 兩種方案,我都寫出來。

Objective-C
//GCD
//需要一個全局變量queue,要讓所有線程的這個操作都加到一個queue中

  dispatch_sync(queue, ^{
      NSInteger ticket = lastTicket;
      [NSThread sleepForTimeInterval:0.1];
      NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
      ticket -= 1;
      lastTicket = ticket;
  });

//NSOperation & NSOperationQueue
重點:
1. 全局的 NSOperationQueue, 所有的操作添加到同一個queue中
2. 設置 queue 的 maxConcurrentOperationCount 爲 1
3. 如果後續操作需要Block中的結果,就需要調用每個操作的waitUntilFinished,阻塞當前線程,一直等到當前操作完成,才允許執行後面的。waitUntilFinished 要在添加到隊列之後!

  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSInteger ticket = lastTicket;
      [NSThread sleepForTimeInterval:1];
      NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
      ticket -= 1;
      lastTicket = ticket;
  }];

  [queue addOperation:operation];

  [operation waitUntilFinished];

//後續要做的事

延遲執行
所謂延遲執行就是延時一段時間再執行某段代碼。下面說一些常用方法。

perform

Objective-C
// 3秒後自動調用self的run:方法,並且傳遞參數:@”abc”

  [self performSelector:@selector(run:) withObject:@"abc" afterDelay:3];

GCD

可以使用 GCD 中的 dispatch_after 方法,OC 和 Swift 都可以使用,這裏只寫 OC 的,Swift 的是一樣的。

Objective-C

// 創建隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 設置延時,單位秒
double delay = 3; 

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{
    // 3秒後需要執行的任務
});

NSTimer

NSTimer 是iOS中的一個計時器類,除了延遲執行還有很多用法,不過這裏直說延遲執行的用法。同樣只寫 OC 版的,Swift 也是相同的。

Objective-C

[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc" repeats:NO];

單例模式
至於什麼是單例模式,我也不多說,我只說說一般怎麼實現。在 Objective-C 中,實現單例的方法已經很具體了,雖然有別的方法,但是一般都是用一個標準的方法了,下面來看看。

Objective-C

@interface Tool : NSObject <NSCopying>

+ (instancetype)sharedTool;

@end

@implementation Tool

static id _instance;

+ (instancetype)sharedTool {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[Tool alloc] init];
    });

    return _instance;
}

@end

Swift

class Tool: NSObject {
    static let sharedTool = Tool()

    // 私有化構造方法,阻止其他對象使用這個類的默認的'()'構造方法
    private override init() {}
}

從其他線程回到主線程的方法
我們都知道在其他線程操作完成後必須到主線程更新UI。所以,介紹完所有的多線程方案後,我們來看看有哪些方法可以回到主線程。

NSThread

//Objective-C

[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];

//Swift
//Swift 取消了 performSelector 方法。
GCD

//Objective-C

dispatch_async(dispatch_get_main_queue(), ^{

});

//Swift

dispatch_async(dispatch_get_main_queue(), { () -> Void in

})

NSOperationQueue

//Objective-C

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

}];

//Swift

NSOperationQueue.mainQueue().addOperationWithBlock { () -> Void in

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