1. 程式人生 > >iOS中的多執行緒

iOS中的多執行緒

多執行緒的一些相關概念

什麼是程序

  • 在系統中正在執行的一個應用程式。
  • 每個程序之間是獨立的,每個程序均執行在其專用而且受保護的記憶體空間內。

什麼是執行緒

  • 一個程序要想執行任務,必須得有一個執行緒,而且每一個程序中至少有一個執行緒
  • 程序的所有任務都線上程中執行

什麼是執行緒的序列

  • 一個執行緒中的任務都是序列執行的
  • 如果要在一個執行緒中執行多個任務,那麼只能一個一個按順序來執行這些任務
  • 同一時間內,一個執行緒只能執行一個任務

比如在一個執行緒中下載幾個檔案(檔案A,檔案B,檔案C) 這裡寫圖片描述

多執行緒

什麼是多執行緒

  • 一個程序中可以開啟多條執行緒,每條執行緒可以並行執行不同的任務

比如同時開啟三條執行緒分別下載幾個檔案(檔案A,檔案B,檔案C) 這裡寫圖片描述

多執行緒原理

1、同一時間,CPU只能處理一條執行緒,只有一條執行緒在工作 2、多執行緒併發,其實是CPU快速的在多條執行緒之間切換 3、如果多執行緒切換速度特別快,就造成了多執行緒併發執行的假象 **注:**如果執行緒非常多,CPU會在N多執行緒之間切換,CPU消耗就會特別大,每條執行緒被呼叫額頻率就會被降低,所有要適當使用多執行緒

多執行緒優缺點

優點

1、能適當的提高程式執行效率 2、能夠適當的提高資源利用率(CPU,記憶體利用率)

缺點

1、建立執行緒是有開銷的,iOS下主要成本包括:核心資料結構(大約1KB),棧空間(子執行緒512KB,主執行緒1MB,也可以使用-setStackSize:設定,但是必須是4K的倍數,而且最小是16K),建立執行緒大約需要90毫秒的建立時間 2、若開啟大量執行緒,會降低執行緒上的效能,CPU消耗越大 3、多執行緒使用太多,會使程式設計更加複雜,比如執行緒間的通訊,資料共享等等

多執行緒在iOS開發中的應用

什麼是主執行緒

  • 一個iOS程式執行後,預設會開啟的一條執行緒,稱為:主執行緒或UI執行緒

主執行緒作用

  • 顯示\重新整理介面
  • 處理UI事件,比如點選事件、滾動事件、拖拽事件等等

主執行緒使用注意

  • 不要把耗時的操作放在主執行緒中
  • 耗時的操作會卡住主執行緒,嚴重影響流暢度

耗時操作的執行如果把耗時操作放在主執行緒裡,當用戶在第5秒時點選按鈕時,因為執行緒裡的操作必須是序列的,此時的這個點選事件會排在10秒耗時操作之後,直到10秒的耗時操作結束後,才能執行按鈕點選事件,這樣就會造成UI卡住的現象,如下圖: 這裡寫圖片描述

如果把耗時操作放在子執行緒裡,此刻,主執行緒和子執行緒同事進行,當用戶在第5秒時點選按鈕時,子執行緒做耗時操作,主執行緒響應介面操作,所以這樣就不會造成UI卡住的現象,所有要把耗時操作放在子執行緒後臺執行緒中,如圖: 這裡寫圖片描述

iOS中多執行緒的實現方案

這裡寫圖片描述

關於Pthread

- (IBAction)buttonClick:(id)sender {
    //PThread的建立
    pthread_t thread;
    pthread_create(&thread, NULL, run, NULL);
    //PThread的建立
    pthread_t thread1;
    pthread_create(&thread1, NULL, run, NULL);
}
void * run(void *param){
    NSLog(@"當前執行緒--%@",[NSThread currentThread]);
    for (NSInteger i = 0; i<5000; i++) {
        NSLog(@"-buttonClick-%ld-%@",(long)i,[NSThread currentThread]);
    }
    return NULL;
}

從程式碼中可以看出Pthread的建立執行其實也是比較簡單的,不過實現過程是通過C語言進行的,從建立方法pthread_create(<#pthread_t _Nullable restrict _Nonnull#>, <#const pthread_attr_t restrict _Nullable#>, <#void _Nullable ( _Nonnull)(void * _Nullable)#>, <#void *restrict _Nullable#>)可以看出,第一個引數是需要一個Pthread 物件指標,第三個是需要一個C語言函式方法(就當於OC中繫結的執行方法),至於第二個和第四個引數,暫時沒有什麼用,可以直接傳入NULL 這裡寫圖片描述 數字1811表示的是當前程式所處的程序 ID 數字27155和27156則表示當前所處的子執行緒 ID number也可以作為執行緒的標識 上述程式碼沒有給執行緒起名字,因此為null 所以我們就可以通過執行緒ID進行判斷是否成功開啟了一個子執行緒

關於NSThread

  • 通過alloc init進行建立
//建立執行緒
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"jack"];
thread.name = @"my-thread";
thread.threadPriority = 0.1;
//啟動執行緒
[thread start];
  • 通過 detachNewThreadSelector 方式建立並執行執行緒
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"rose"];
  • 隱式建立後自動啟動執行緒
[self performSelectorInBackground:@selector(run:) withObject:@"wahaha"];
採用第一種方式:設定一些執行緒屬性;例如執行緒 名字,從控制檯資訊可以看出來,當設定了不同的NSThread物件的優先順序屬性,可以控制其執行的順序,優先順序越高,越先執行;而設定名字屬性後,可以通過除錯監控當前所處執行緒,便於問題分析
第二、三種,建立和操作簡單
  • 執行緒的狀態

    當我們新建一個執行緒物件的時候,系統就會為其分配一塊記憶體,當你呼叫執行緒的開始方法時,就相當於把這個執行緒放在了執行緒池裡面,等待CPU去呼叫它,當執行緒池中有多個執行緒,那麼CPU就會在這幾個執行緒之間來回切換,但是當執行緒呼叫了sleep或同步鎖時,該呼叫的執行緒就會被阻塞,當sleep或鎖結束時,CPU再次進行切換中,當執行緒任務執行完,該執行緒就會釋放。

@property (class, readonly, strong) NSThread *currentThread// 獲取當前執行緒
+ (NSThread *)mainThread; // 獲得主執行緒
- (BOOL)isMainThread; // 是否為主執行緒
+ (BOOL)isMainThread; // 是否為主執行緒
- (void)start; // 進入就緒狀態 -> 執行狀態。當執行緒任務執行完畢,自動進入死亡狀態
+ (void)sleepUntilDate:(NSDate *)date;// 進入阻塞狀態
+ (void)sleepForTimeInterval:(NSTimeInterval)time;// 進入阻塞狀態
+ (void)exit;//強制停止執行緒,一旦執行緒停止(死亡)了,就不能再次開啟任務

多執行緒的競爭(執行緒鎖)

一塊資源可能被多個執行緒共享,也就是多個執行緒可能會訪問同一個資源、同一個物件、同一個變數,就會出現執行緒安全問題 這裡寫圖片描述 當執行緒A去訪問檔案時,需要將檔案鎖住,讀取並運算結束後,將其解鎖,執行緒B再去訪問,執行緒B訪問時需要再次將檔案加鎖並運算,結束後再解鎖。這樣就避免了倆個執行緒同時訪問檔案而造成檔案錯誤。

互斥鎖使用

  • 格式:@synchronized(鎖物件) { // 需要鎖定的程式碼 }
  • **互斥鎖的使用前提:**多條執行緒搶奪同一塊資源
  • **注意:**鎖定1份程式碼只用1把鎖,用多把鎖是無效的
  • 互斥鎖的優缺點
    • 優點:能有效防止因多執行緒搶奪資源造成的資料安全問題
    • 缺點:需要消耗大量的CPU資源
  • 執行緒同步
    • 執行緒同步的意思是:多條執行緒在同一條線上執行(按順序地執行任務)
    • 互斥鎖,就是使用了執行緒同步技術,保證執行緒的序列
  • 執行緒非同步
    • 多執行緒預設的就是非同步,這個不需要多做解釋
  • 原子和非原子屬性
    • atomic:原子屬性,預設為setter方法加鎖(預設就是atomic),執行緒安全,需要消耗大量的資源
    • nonatomic:非原子屬性,不會為setter方法加鎖,適合記憶體小的移動裝置
    • 注:iOS開發中,所有屬性都宣告為nonatomic,儘量避免多執行緒搶奪同一資源。儘量將加鎖,資源搶奪業務交給伺服器

執行緒間通訊

  • 執行緒間通訊 在一個程序中,執行緒往往不是孤立存在,多個執行緒需要經常進行通訊,一般表現為一個執行緒傳遞資料給另外一個執行緒,或者在一個執行緒中執行完一個特定任務後,轉到另一個執行緒繼續執行任務
  • 執行緒間通訊常用方法
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
    
     - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
    
    
  • 示例 這裡寫圖片描述 處理UI在主執行緒,下載圖片載子執行緒,當圖片下載完後,迴歸主執行緒並顯示。
- (IBAction)showImageView:(UIButton *)sender {
    NSLog(@"%@",[NSThread currentThread]);
    [self performSelectorInBackground:@selector(download) withObject:nil];
}

-(void)download{
    NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image =[UIImage imageWithData:data];
    NSLog(@"%@",[NSThread currentThread]);
    [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
}

-(void)showImage:(UIImage *)image{
    self.imageView.image = image;
    NSLog(@"%@",[NSThread currentThread]);
}

關於GCD

  • 什麼是GCD 全稱Grand Central Dispatch,純C語言,提供了非常大的函式
  • GCD優勢 CGD是蘋果公司為多核的並行運算提供的方案(iOS4開始) GCD會自動利用更多的CPU核心 GCD會自動管理執行緒生命週期(建立執行緒、排程執行緒、銷燬執行緒)程式設計師只需要告訴GCD想要執行什麼任務,不需要編寫任何執行緒管理的程式碼
  • GCD倆個核心概念 任務:執行什麼操作 佇列:用來存放任務
  • GCD使用 定製任務(想做的操作) 將任務新增到隊列當中(GCD會自動將佇列中的任務取出,放到對應的執行緒中執行,任務的取出遵循佇列的FIFO原則,先進先出,後進後出。)
  • GCD執行
    //同步方式執行
    dispatch_sync(dispatch_queue_t  _Nonnull queue, <^(void)block>)
    //非同步的方式執行
    dispatch_async(dispatch_queue_t  _Nonnull queue, <^(void)block>)
    
    queue:佇列
    block:任務
    
    
  • 同步和非同步的區別:
    • 同步:只能在當前執行緒中執行任務,不具備開啟新執行緒的能力
    • 非同步:可以在新的執行緒中執行任務,具備開啟新執行緒的能力
  • GCD的佇列型別
    • 併發佇列:可以讓多個任務併發執行,併發功能只有在非同步dispatch_async函式下才有效
    • 序列佇列:讓任務一個接著一個地執行

注:有四個容易混淆的概念:同步、非同步、併發、序列 同步和非同步主要影響能不能開執行緒 序列和並行主要影響任務的執行方式

GCD併發佇列

  • 非同步函式 + 併發佇列:會開新執行緒
	//1、建立一個併發佇列
	//第一種:自定義(佇列的名字,佇列的型別)
	//佇列的型別(併發:DISPATCH_QUEUE_CONCURRENT,序列:DISPATCH_QUEUE_SERIAL)
    //    dispatch_queue_t queue = dispatch_queue_create("com.xiaoyi.queue", DISPATCH_QUEUE_CONCURRENT);
    
//第二種:獲得全域性的併發佇列(引數1:優先順序(官方建議用default),引數2:目前沒有意義,官方文件提示傳0)
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //2、將任務加入佇列
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<5; i++) {
            NSLog(@"1--%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<5; i++) {
            NSLog(@"2--%@",[NSThread currentThread]);
        }
    });
   

這裡寫圖片描述

  • 同步函式 + 併發佇列 :不會開新執行緒
	//1、建立一個併發佇列
	
	//第一種:自定義(佇列的名字,佇列的型別)
	//佇列的型別(併發:DISPATCH_QUEUE_CONCURRENT,序列:DISPATCH_QUEUE_SERIAL)
	//    dispatch_queue_t queue = dispatch_queue_create("com.xiaoyi.queue", DISPATCH_QUEUE_CONCURRENT);
    
	//第二種:獲得全域性的併發佇列(引數1:優先順序(官方建議用default),引數2:目前沒有意義,官方文件提示傳0)
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //2、將任務加入佇列
    dispatch_sync(queue, ^{
        for (NSInteger i = 0; i<5; i++) {
            NSLog(@"1--%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (NSInteger i = 0; i<5; i++) {
            NSLog(@"2--%@",[NSThread currentThread]);
        }
    });

這裡寫圖片描述

GCD序列佇列

  • 非同步函式 + 序列佇列 :會開新執行緒,但是任務是序列的,執行完一個再執行下一個
//1、建立一個序列佇列(沒有全域性,只能建立)

	//第一種:佇列的名字,佇列的型別
    //佇列的型別(併發:DISPATCH_QUEUE_CONCURRENT,序列:DISPATCH_QUEUE_SERIAL)
    dispatch_queue_t queue = dispatch_queue_create("com.xiaoyi.queue", DISPATCH_QUEUE_SERIAL);
    
    //第二種:獲得全域性的併發佇列(引數1:佇列的名字,引數2:佇列的型別(NULL)
//    dispatch_queue_t queue = dispatch_queue_create("com.xiaoyi.queue", NULL);

    //2、將任務加入佇列
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<5; i++) {
            NSLog(@"1--%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<5; i++) {
            NSLog(@"2--%@",[NSThread currentThread]);
        }
    });

這裡寫圖片描述

  • 同步函式 + 序列佇列 :不會開新執行緒,在當前執行緒執行任務
   //1、建立一個序列佇列(沒有全域性,只能建立)

	//第一種:佇列的名字,佇列的型別
    //佇列的型別(併發:DISPATCH_QUEUE_CONCURRENT,序列:DISPATCH_QUEUE_SERIAL)
    dispatch_queue_t queue = dispatch_queue_create("com.xiaoyi.queue", DISPATCH_QUEUE_SERIAL);
    
    //第二種:獲得全域性的併發佇列(引數1:佇列的名字,引數2:佇列的型別(NULL)
//    dispatch_queue_t queue = dispatch_queue_create("com.xiaoyi.queue", NULL);]

    //2、將任務加入佇列
    dispatch_sync(queue, ^{
        for (NSInteger i = 0; i<5; i++) {
            NSLog(@"1--%@",[NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        for (NSInteger i = 0; i<5; i++) {
            NSLog(@"2--%@",[NSThread currentThread]);
        }
    });

這裡寫圖片描述

GCD主佇列(特殊的序列佇列)

  • 放在主佇列中的任務,都會放在主執行緒中執行

  • 使用dispatch_get_main_queue()獲得主佇列

  • 非同步函式 + 主佇列 :只在主執行緒中執行任務

   //獲取主佇列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    //2、將任務加入佇列
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<5; i++) {
            NSLog(@"1--%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<5; i++) {
            NSLog(@"2--%@",[NSThread currentThread]);
        }
    });

這裡寫圖片描述

  • 同步函式 + 主佇列 :會卡死,執行緒間相互制約
//獲取主佇列
dispatch_queue_t queue = dispatch_get_main_queue();
//2、將任務加入佇列
dispatch_sync(queue, ^{
    for (NSInteger i = 0; i<5; i++) {
        NSLog(@"1--%@",[NSThread currentThread]);
    }
});
    
dispatch_sync(queue, ^{
    for (NSInteger i = 0; i<5; i++) {
       NSLog(@"2--%@",[NSThread currentThread]);
    }
});

這裡寫圖片描述

  • 各種佇列的執行效果 這裡寫圖片描述 注: 1、使用同步函式往當前序列佇列中新增任務,會卡住當前的序列佇列 2、使用非同步函式是需要把當前方法執行,再去執行非同步函式

  • GCD執行緒間通訊

//圖片子執行緒下載
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
        NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image =[UIImage imageWithData:data];
        
        //圖片主執行緒顯示
        dispatch_sync(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
        });
    });
  • barrier函式 在barrier函式前面執行的任務執行結束後它後面的任務才會執行 這個queue不能是全域性的佇列,最好自己建立,例如:
dispatch_queue_t queue = dispatch_queue_create("com.xiaoyi.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    for (NSInteger i = 0; i<3; i++) {
        NSLog(@"1----%@",[NSThread currentThread]);
    }
});
dispatch_async(queue, ^{
    for (NSInteger i = 0; i<3; i++) {
        NSLog(@"2----%@",[NSThread currentThread]);
    }
});
    
dispatch_barrier_sync(queue, ^{
    NSLog(@"--barrier--%@",[NSThread currentThread]);
});
    
dispatch_async(queue, ^{
    for (NSInteger i = 0; i<3; i++) {
        NSLog(@"3----%@",[NSThread currentThread]);
    }
});
dispatch_async(queue, ^{
    for (NSInteger i = 0; i<3; i++) {
       NSLog(@"4----%@",[NSThread currentThread]);
    }
});

這裡寫圖片描述

  • 延時函式
	//第一種延時
    [self performSelector:@selector(run) withObject:nil afterDelay:2];
    //第二種延時
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"run");
    });
    //第三種延時
    [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(run) userInfo:nil repeats:NO];
    NSLog(@"start");
  • 一次性程式碼
	static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"--程式在整個載入過程只會載入一次");
    });
  • 快速迭代(主要用於併發佇列)
//引數1: 指定重複次數
//引數2:物件的DispatchQueue
//引數3:帶有引數的Block, index的作用是為了按執行的順序區分各個Block
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%@--%zu",[NSThread currentThread],index);
    });
NSLog(@"done");

這裡寫圖片描述

GCD佇列組

  • 佇列組就是可以對多個佇列進行操作的一個組,在佇列組中可以對不同佇列進行操作監聽結果等等
	 //建立一個佇列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //建立一個佇列組
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"download image1 start");
        //下載圖片1
        NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        self.image1 =[UIImage imageWithData:data];
        NSLog(@"download image1 end");
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"download image2 start");
        //下載圖片2
        NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        self.image2 =[UIImage imageWithData:data];
        NSLog(@"download image2 end");
    });
    
    //當這個佇列組的所有佇列全部完成,就會收到這個訊息
    dispatch_group_notify(group, queue, ^{
        NSLog(@"download all images");
        //合成新圖片
        UIGraphicsBeginImageContext(CGSizeMake(100, 100));
        [self.image1 drawInRect:CGRectMake(0, 0, 50, 100)];
        [self.image2 drawInRect:CGRectMake(50, 0, 50, 100)];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        //在主執行緒顯示
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
        });
    });

關於NSOperation

  • 配合使用NSOperation和NSOperationQueue也能實現多執行緒程式設計
    • 先將需要執行的操作封裝到一個NSOperation物件中
    • 然後將NSOperation物件新增到NSOperationQueue中
    • 系統會自動將NSOperationQueue中的NSOperation取出來
    • 將取出的NSOperation封裝的操作放到一條新執行緒中執行
  • NSOperation的子類(抽象類,並不具備封裝操作的能力,必須使用它的子類)
    • NSInvocationOperation
    • NSBlockOperation
    • 自定義子類繼承NSOperation,實現內部相應的方法
  • 關於NSInvocationOperation 呼叫start方法開始執行操作,一旦執行操作就會呼叫run方法 注:預設情況下,呼叫了start方法後並不會開一條新執行緒去執行操作,而是在當前執行緒同步執行操作 只有NSOperation放到一個NSOperationQueue中,才會非同步執行
- (IBAction)invocationOperation:(id)sender {
    //初始化Operation子類
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];
    //開啟
    [operation start];
}
-(void)run{
    NSLog(@"0--%@",[NSThread currentThread]);
}

這裡寫圖片描述

  • 關於NSBlockOperation 注:只要NSBlockOperation封裝的運算元大於1,就會非同步執行
    //初始化Operation子類
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1--%@",[NSThread currentThread]);
    }];
    //新增額外的任務(在子執行緒執行)
    [operation addExecutionBlock:^{
        NSLog(@"2--%@",[NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        NSLog(@"3--%@",[NSThread currentThread]);
    }];
    [operation start];

這裡寫圖片描述

  • 關於NSOperationQueue
    • NSOperationQueue的作用
      • NSOperation可以呼叫start方法來執行任務,預設是同步的
      • 如果將NSOperation新增到NSOperationQueue(操作佇列)中,系統會自動非同步執行NSOperation中的操作
//建立佇列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//NSOperationQueue *queue = [NSOperationQueue mainQueue];

    //建立操作(任務)
    //建立--NSInvocationOperation
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run1) object:nil];
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run2) object:nil];
    //建立--NSBlockOperation
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"-1--%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"-2--%@",[NSThread currentThread]);
    }];
    
    //自定義(需要繼承NSOperation,執行的操作需要放在這個自定義類的main中)
    SSOperation *op5 = [[SSOperation alloc]init];
    
    //新增任務佇列中
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];
    [queue addOperation:op5];
    //也可以直接建立任務到佇列中去
    [queue addOperationWithBlock:^{
        NSLog(@"-3--%@",[NSThread currentThread]);
    }];

通過輸出的log可以看出確實有開啟執行緒,所有隻要將操作新增的佇列裡,就可以實現多執行緒操作 這裡寫圖片描述

  • NSOperationQueue設定最大併發運算元
    //建立佇列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    //設定最大併發運算元(不管加入佇列有多少操作,實際佇列併發數為3)
    queue.maxConcurrentOperationCount = 3;
    
//    //設定為1就成了序列佇列
//    queue.maxConcurrentOperationCount = 1;
    
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
        NSLog(@"-1--%@",[NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
        NSLog(@"-2--%@",[NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
        NSLog(@"-3--%@",[NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
        NSLog(@"-4--%@",[NSThread currentThread]);
    }];

這裡寫圖片描述

  • NSOperationQueue設定佇列掛起與取消
    • 當佇列呼叫了佇列掛起的方法( self.queue.suspended = YES;),佇列裡的執行方法立即停止,但是有一點需要注意的是,當block操作中,佇列掛起是不起作用的,它是無法停止的,必須操作執行結束後才會生效。
    • 當佇列呼叫取消( [self.queue cancelAllOperations])就意味著後續佇列不再執行,再次啟動需要重新加入佇列
#pragma mark-OperationQueue相關設定--設定佇列掛起(暫停)
- (IBAction)createOperationQueueSuspended:(id)sender {

    //建立佇列
    self.queue = [[NSOperationQueue alloc]init];
    self.queue.maxConcurrentOperationCount = 1;
    [self.queue addOperationWithBlock:^{
        NSLog(@"-1--%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];
    }];
    [self.queue addOperationWithBlock:^{
        NSLog(@"-2--%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];
    }];
    [self.queue addOperationWithBlock:^{
        NSLog(@"-3--%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];
    }];
    [self.queue addOperationWithBlock:^{
        NSLog(@"-4--%@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];
    }];
    //設定佇列掛起或者取消的話都必須是在block方法執行完之後才有效
    [self.queue addOperationWithBlock:^{
        for(NSInteger i = 0;i<5;i++){
            NSLog(@"-5--%zd---%@",(long)i,[NSThread currentThread]);
        }
    }];
}
#pragma mark-設定佇列掛起
- (IBAction)operationSetSuspended:(id)sender {
    if(self.queue.suspended){
        //恢復佇列,繼續執行
        self.queue.suspended  = NO;
    }else{
        //掛起(暫停佇列)
        self.queue.suspended  = YES;
    }
}
#pragma mark-設定佇列取消(取消就意味著後續佇列不再執行,再次啟動需要重新加入佇列)
- (IBAction)operationSetCancel:(id)sender {
    [self.queue cancelAllOperations];
}
  • NSOperationQueue設定佇列監聽與依賴
 NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"down1---%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"down2---%@",[NSThread currentThread]);
        
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{

        NSLog(@"down3---%@",[NSThread currentThread]);
        
    }];
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
       
        NSLog(@"down4---%@",[NSThread currentThread]);
    }];
    
    
    //設定依賴(op1和op3執行完之後才執行2)
    [op3 addDependency:op1];
    [op3 addDependency:op4];
    
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];

    
    //監聽一個操作的執行完成
    [op3 setCompletionBlock:^{
        NSLog(@"執行完成");
    }];

這裡寫圖片描述

注:一定要避免相互依賴,比如

[op3 addDependency:op1];
[op1 addDependency:op3];    //錯誤的寫法---相互依賴
  • NSOperationQueue佇列間的資料通訊 先建立了一個普通佇列,在普通佇列裡執行倆個操作,當子執行緒的圖片都下載下來後,迴歸主執行緒將其顯示在UI介面上。
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    __block UIImage *image1;
    NSBlockOperation *downloadw1 = [NSBlockOperation blockOperationWithBlock:^{
        
        //下載圖片1
        NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        image1 =[UIImage imageWithData:data];
    }];
    
    __block UIImage *image2;
    NSBlockOperation *downloadw2 = [NSBlockOperation blockOperationWithBlock:^{
        
        //下載圖片2
        NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        image2 =[UIImage imageWithData:data];
        
    }];
    
    
    NSBlockOperation *combine = [NSBlockOperation blockOperationWithBlock:^{
        
        //合成新圖片
        UIGraphicsBeginImageContext(CGSizeMake(100, 100));
        [image1 drawInRect:CGRectMake(0, 0, 50, 100)];
        [image2 drawInRect:CGRectMake(50, 0, 50, 100)];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            self.imageView.image = image;
        }];
    }];
    
    [combine addDependency:downloadw1];
    [combine addDependency:downloadw2];
    
    [queue addOperation:downloadw1];
    [queue addOperation:downloadw2];
    [queue addOperation:combine];