天天看點

iOS多線程知識點NSThreadGCDNSOperationGCD對比NSOperation

最近整理的iOS多線程方面的知識點,iOS中總共有4種實作多線程的方案,但是pthread是基于C語言并且不太好用,是以很少人用,是以也沒啥好講的。

歡迎指錯以及補充

文章目錄

  • NSThread
      • 3種建立線程方法
      • 3種方法對比
  • GCD
      • 1. 6種組合:
      • 2. 一次性函數:
      • 3. 延遲執行:
      • 4. 隊列組
      • 5. 快速疊代
      • 6. 線程間通信
      • 7. 栅欄函數
  • NSOperation
      • 1. 自定義隊列和主隊列
      • 2. 建立步驟
      • 3. 特點
      • 4. NSBlockOperation
      • 5. 線程間通信
      • 6. 操作依賴和監聽
  • GCD對比NSOperation

NSThread

3種建立線程方法

  • NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
  • [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
  • [self performSelectorInBackground:@selector(run) withObject:nil];

3種方法對比

  • 第一種方法可以拿到線程對象thread,可以設定thread的一些特性,比如優先級,名稱;後兩種方法均拿不到線程對象。

GCD

1. 6種組合:

  • 同步函數+串行隊列:不會開啟子線程,所有的任務在主線程串行執行
  • 同步函數+并發隊列:不會開啟子線程,所有的任務在主線程串行執行
  • 同步函數+主隊列:會發生死鎖
  • 異步函數+串行隊列:會開啟一條子線程,所有的任務在子線程串行執行
  • 異步函數+并發隊列:會開啟n條子線程,所有的任務在n條子線程并發執行
  • 異步函數+主隊列:不會開啟子線程,所有的任務在主線程中串行執行

2. 一次性函數:

static dispatch_once_t onceToken;
    // NSLog(@"%ld", onceToken);
    // 原理是判斷onceToken的值,若等于0則執行,若等于-1則不執行
    dispatch_once(&onceToken, ^{
        NSLog(@"once");
    });
           

3. 延遲執行:

[self performSelector:@selector(run) withObject:nil afterDelay:2.0];

[NSTimer scheduledTimerInterval:2.0 target:self selector:(run) userInfo:nil repeats:YES];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@", [NSThread currentThread]);
    });

           

4. 隊列組

  • 建立一個隊列組
dispatch_group_t group = dispatch_group_create();
           
  • 建立一個隊列
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
           
  • 封裝任務
dispatch_group_async(group, queue, ^{
        NSURL *url1 = [NSURL URLWithString:@"https://ww3.sinaimg.cn/mw1024/bfa7a89ejw1er5utf4cr9j20qo0zke4v.jpg"];
        
        NSData *imageData1 = [NSData dataWithContentsOfURL:url1];
        
        self.image1 = [UIImage imageWithData:imageData1];
        
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
---------------------------------------------------------------
dispatch_group_async(group, queue, ^{
        NSURL *url2 = [NSURL URLWithString:@"https://ww4.sinaimg.cn/mw1024/bfa7a89ejw1erp7r1nttgj20qo0zkq8g.jpg"];
        
        NSData *imageData2 = [NSData dataWithContentsOfURL:url2];
        
        self.image2 = [UIImage imageWithData:imageData2];
        
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
           
  • 監聽通知(當隊列組中的任務執行完畢後,建立圖形上下文)
dispatch_group_notify(group, queue, ^{
        UIGraphicsBeginImageContext(CGSizeMake(300, 300));
        
        [self.image1 drawInRect:CGRectMake(0, 0, 150, 300)];
        [self.image2 drawInRect:CGRectMake(150, 0, 150, 300)];
        
        NSLog(@"combine-----%@", [NSThread currentThread]);
        
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        
        UIGraphicsEndImageContext();
        
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            
            NSLog(@"UI-----%@", [NSThread currentThread]);
        });
    });
           

5. 快速疊代

- (void)test2
{
    NSString *from = @"/Users/AngusGray/Desktop/from";
    NSString *to = @"/Users/AngusGray/Desktop/to";
    
    NSArray *subPaths = [[NSFileManager defaultManager] subpathsAtPath:from];
    
    dispatch_apply(subPaths.count, dispatch_get_global_queue(0, 0), ^(size_t i) {
        NSString *fromFullPath = [from stringByAppendingPathComponent:subPaths[i]];
        NSString *toFullPath = [to stringByAppendingPathComponent:subPaths[i]];
        
        [[NSFileManager defaultManager] moveItemAtPath:fromFullPath toPath:toFullPath error:nil];
        
        NSLog(@"%@-----%@-----%@", fromFullPath, toFullPath, [NSThread currentThread]);
    });
}
           

6. 線程間通信

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 異步函數 + 串行|并發隊列
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 設定URL
        NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1552289582393&di=bcd6e443745c4839107b46e260e9ea32&imgtype=0&src=http%3A%2F%2Fimg.12584.cn%2Fimages%2F3718642-20170112230345230.jpg"];
        
        // 将URL中的圖檔轉換為二進制資料
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        
        NSLog(@"download-----%@", [NSThread currentThread]);
        // 将二進制資料轉化為圖檔(在主線程中進行)
        // 異步函數|同步函數 + 主隊列
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = [UIImage imageWithData:imageData];
            NSLog(@"UI-----%@", [NSThread currentThread]);
        });
    });
}
           

7. 栅欄函數

dispatch_barrier_async(queue, ^{
        NSLog(@"+++++");
    });
           

NSOperation

1. 自定義隊列和主隊列

  • 自定義隊列:[[NSOperationQueue alloc] init];

    特點:預設并發隊列,但是可以控制讓它成為一個串行隊列。

  • 主隊列:[NSOperationQueue mainQueue];

    特點:串行隊列,和主線程相關(凡是放在主隊列中的任務都在主線程執行)。

2. 建立步驟

  • 建立隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
           
  • 封裝操作對象
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op1-----%@", [NSThread currentThread]);
    }];
           
  • 把操作添加到隊列中
// 該方法内部會自動調用start方法
[queue addOperation:op1]; 
           
  • 簡便方法:該方法内部首先會把block中的任務封裝成一個操作(Operation),然後把該操作直接添加到隊列
[queue addOperationWithBlock:^{
        NSLog(@"op1-----%@", [NSThread currentThread]);
    }];
           

3. 特點

  • 可以設定最大并發數
queue.maxConcurrentOperationCount = 1; // 等同于串行執行
           
  • 可以暫停隊列中的操作(隻能暫停正在執行操作後面的操作,目前操作不可分割必須執行完畢
[self.queue setSuspended:YES];
           
  • 恢複暫停的操作
[self.queue setSuspended:NO];
           
  • 取消所有操作(隻能取消隊列中所有正在等待的操作
[self.queue cancelAllOperations];
           
  • 将操作添加到隊列可使用數組的方式(如果使用YES,則目前線程阻塞直到所有操作完成)
[queue addOperations:@[op1, op2, op3] waitUntilFinished:NO];
           
  • 可以通過重寫内部的main方法類可以自定義操作任務(自定義操作的好處:代碼複用)
-(void)main
{
    for (int i = 0; i < 10000; i++) {
        NSLog(@"main1--%d--%@", i, [NSThread currentThread]);
    }
    
    //官方建議:在自定義操作的時候每執行完一次耗時操作就判斷一下目前操作是否被取消,如果被取消則直接傳回
    if (self.cancelled == YES) {
        return;
    }
    for (int i = 0; i < 10000; i++) {
        NSLog(@"main2--%d--%@", i, [NSThread currentThread]);
    }
    
    if (self.cancelled == YES) {
        return;
    }
    
    for (int i = 0; i < 10000; i++) {
        NSLog(@"main3--%d--%@", i, [NSThread currentThread]);
    }
}
           

4. NSBlockOperation

  • 本身不開子線程,添加額外任務時會開子線程
  • 當操作中任務數量>1時,會開啟多條子線程和目前線程一起工作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"op1-----%@", [NSThread currentThread]);
    }];
    
[op1 addExecutionBlock:^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    }];

[op1 start]; // 需要手動開始
           

5. 線程間通信

  • 在子線程中調用主線程執行任務
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
          do something;
        }];
           

6. 操作依賴和監聽

  • 給操作添加依賴op4->op3->op2->op1
[op1 addDependency:op2];
[op2 addDependency:op3];
[op3 addDependency:op4];
           
  • 當操作完成可使用completionBlock監聽
op4.completionBlock = ^{
        do something;
    };
           

GCD對比NSOperation

  1. GCD是純C語言的API,而NSOperation是OC的對象。
  2. GCD中任務用塊(block)封裝,而NSOperation中任務用操作封裝,block比操作更加輕量級。
  3. GCD有栅欄函數、延遲執行和快速疊代
  4. NSOperationQueue可以友善地調用cancel方法來取消某個操作,而GCD中的任務是無法被取消的。
  5. NSOperation可以友善地指定操作間的依賴關系
  6. NSOperation可以通過KVO達到對操作的精細控制,比如監聽操作是否被取消或者已經完成。
  7. NSOperation可以友善地指定操作優先級。
  8. 通過自定義NSOperation的子類可以實作操作複用。