iOS開發(fā)之多線程:Pthread、NSThread、GCD、NSOperation、NSOperationQueue


進程

什么是進程?
  • 進程是指在系統(tǒng)中 正在運行 的一個應(yīng)用程序。
  • 每個進程之間是 獨立 的,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi)。
    比如同時打開QQ音樂、Xcode,系統(tǒng)就會分別啟動2個進程
    thread-01.png
通過“活動監(jiān)視器”可以查看Mac系統(tǒng)中所開啟的進程

線程

什么是線程?
  • 1個進程要想執(zhí)行 任務(wù),必須得有線程 (每1個進程至少要有1條線程)。
  • 線程是進程的基本執(zhí)行單元,一個進程(程序)的所有任務(wù)都在線程中執(zhí)行。
    比如使用酷狗播放音樂、使用迅雷下載電影,都需要在線程中執(zhí)行
    thread-02.png
線程的串行
  • 1個線程中任務(wù)的執(zhí)行是 串行 的;
  • 如果要在1個線程中執(zhí)行多個任務(wù),那么只能一個一個地按順序執(zhí)行這些任務(wù);
  • 也就是說,在同一時間內(nèi),1個線程只能執(zhí)行1個任務(wù)。
    比如在1個線程中下載3個文件(分別是文件A、文件B、文件C)
    thread-03.png

    因此,也可以認(rèn)為 線程是進程中的1條執(zhí)行路徑
進程和線程的比較
  • 線程是CPU調(diào)用(執(zhí)行任務(wù))的最小單位。
  • 進程是CPU分配資源和調(diào)度的單位。
  • 一個程序可以對應(yīng)多個進程,一個進程中可以有多個線程,但至>少要有一個線程。
  • 同一個進程內(nèi)的線程共享進程的資源。

多線程

基本概念
  • 1個進程中可以開啟多條線程,每條線程可以 并行(同時)執(zhí)行不同的任務(wù)。
線程的并行:
  • 并行即同時執(zhí)行。
    比如同時開啟3條線程分別下載3個文件(分別是文件A、文件B、文件C)
    thread-04.png
多線程并發(fā)執(zhí)行的原理:
  • 同一時間,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)。
  • 多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間 調(diào)度(切換)。
  • 如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象。
  • 思考:如果線程非常非常多,會發(fā)生什么情況?

1.CPU會在N多線程之間調(diào)度,CPU會累死,消耗大量的CPU資源。
2.每條線程被調(diào)度執(zhí)行的頻次會降低(線程的執(zhí)行效率降低)。

多線程的優(yōu)缺點:
  • 優(yōu)點

1、能適當(dāng)提高程序的執(zhí)行效率。
2、能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)。

  • 缺點

1、創(chuàng)建線程是有開銷的,iOS下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)、棧空間(子線程512KB、主線程1MB,也可以使用-setStackSize:設(shè)置,但必須是4K的倍數(shù),而且最小是16K),創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間。
2、如果開啟大量的線程,會降低程序的性能。
3、線程越多,CPU在調(diào)度線程上的開銷就越大。
4、程序設(shè)計更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享。


多線程在iOS開發(fā)中的應(yīng)用

主線程:
  • 一個iOS程序運行后,默認(rèn)會開啟1條線程,稱為 主線程”或“UI線程”。
主線程的主要作用:
  • a、顯示\刷新UI界面。
  • b、處理UI事件(比如點擊事件、滾動事件、拖拽事件等)。
主線程的使用注意 !
  • a、不要將比較耗時的操作放到主線程中(耗時操作會卡住主線程,嚴(yán)重影響UI的流暢度,給用戶一種“卡”的壞體驗)。
  • b、UI必須放在主線程中處理。
耗時操作的執(zhí)行
  • a、如果將耗時操作放在 主線程
    thread-05.png
問題在哪?

1.在用戶點擊按鈕5秒后才給出反應(yīng)

  • b、如果將耗時操作放在 子線程(后臺線程、非主線程)。
    thread-06.png
  • 好處在哪?

1.在用戶點擊按鈕那一刻就有反應(yīng)。
2.能同時處理耗時操作和用UI控件的事件。


iOS開發(fā)中 多線程 的實線方案

技術(shù)方案 簡介 語言 線程生命周期 使用頻率
pthread a.一套通用的多線程API
b.適用于Unix\Linux\Windows等系統(tǒng);
c.跨平臺\可移植
d.使用難度大
C 程序員管理 幾乎不用
NSThread a.使用更加面向?qū)ο?br>b.簡單易用,可直接操作線程對象 OC 程序員管理 偶爾使用
GCD a.旨在替代NSThread等線程技術(shù)
b.充分利用設(shè)備的多核
C 自動管理 經(jīng)常使用
NSOperation a.基于GCD(底層是GCD)
b.比GCD多了一些更單實用的功能
c.使用更加面向?qū)ο?/td>
OC 自動管理 經(jīng)常使用

pthread
  • pthread的基本使用
#import "ViewController.h"
#import <pthread.h>

@interface ViewController ()

@end

@implementation ViewController

- (IBAction)btnClick:(id)sender {
    
    NSLog(@"mainThread:%@",[NSThread mainThread]);
    
    //pthread創(chuàng)建線程,執(zhí)行任務(wù)
    //01 包含頭文件
    //02 創(chuàng)建線程對象
    
    pthread_t thread = nil;
    
    //03 創(chuàng)建線程,執(zhí)行任務(wù)
    /* 參數(shù)說明
     *
     * 第一個參數(shù):線程對象 傳地址
     * 第二個參數(shù):線程的屬性 (優(yōu)先級)
     * 第三個參數(shù):指向函數(shù)的指針
     * 第四個參數(shù):傳給第三個參數(shù)的(參數(shù))
     */
    pthread_create(&thread, NULL, run, NULL);
}

//技巧:(*)改寫成函數(shù)的名稱,補全參數(shù)
void *run(void *str)
{
    //NSLog(@"run-------%@",[NSThread currentThread]);
    
    //把耗時操作放在子線程中執(zhí)行
    for (int i = 0; i < 1000000; ++i) {
        NSLog(@"%zd---%@",i,[NSThread  currentThread]);
    }
    
    return NULL;
}
//$\color{red}{正在運行}$
@end

NSThread
  • 一個NSThread對象就代表一條線程
  • 主線程相關(guān)用法
+ (NSThread *)mainThread; // 獲得主線程
- (BOOL)isMainThread; // 是否為主線程
+ (BOOL)isMainThread; // 是否為主線程
  • 獲得當(dāng)前線程
NSThread *current = [NSThread currentThread];
  • 創(chuàng)建、啟動線程:
  • 創(chuàng)建線程方式1
/**
 * 需要手動調(diào)用 start方法開啟線程
 */
-(void)createThread1
{
    //01 創(chuàng)建線程對象
    /* 參數(shù)說明
     *
     * 第一個參數(shù):目標(biāo)對象
     * 第二個參數(shù):方法選擇器 要執(zhí)行的任務(wù)(方法)
     * 第三個參數(shù):調(diào)用函數(shù)需要傳遞的參數(shù)
     */
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
    
    //02 啟動線程
    [thread start];
}
  • 創(chuàng)建線程方式2
//創(chuàng)建線程后自動啟動線程
-(void)createThread2
{
    //直接分離出一條子線程
    [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}
  • 創(chuàng)建線程方式3
//隱式創(chuàng)建并啟動線程
-(void)createThread3
{
    //開啟后臺線程
    [self performSelectorInBackground:@selector(run) withObject:nil];
}
  • 三種創(chuàng)建線程方式對比
方式1:代碼量更大|能夠拿到線程對象(需要手動開啟)
方式2:分離出子線程(不需要手動開啟),無法拿到線程對象進行詳細設(shè)置(名字|優(yōu)先級)
方式3:開啟后臺線程 (不需要手動開啟),無法拿到線程對象進行詳細設(shè)置(名字|優(yōu)先級)
  • 線程的屬性設(shè)置(名稱|優(yōu)先級)
//設(shè)置線程的名字
- (void)setName:(NSString *)name;
- (NSString *)name;

//設(shè)置線程的優(yōu)先級  `范圍 0.0~1.0`
// 默認(rèn)是0.5 
//1.0最高的
//優(yōu)先級更高的線程,被CPU調(diào)度到的概率會更高
- (void)setThreadPriority:(double)priority;
- (double)threadPriority;
  • 線程的生命周期:當(dāng)線程內(nèi)部的任務(wù)執(zhí)行完畢會被釋放
  • 線程的狀態(tài)
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
thread-07.png
  • 控制線程狀態(tài)
啟動線程
- (void)start; 
// 進入就緒狀態(tài) -> 運行狀態(tài)。當(dāng)線程任務(wù)執(zhí)行完畢,自動進入死亡狀態(tài)
阻塞(暫停)線程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 進入阻塞狀態(tài)
強制停止線程
+ (void)exit;
// 進入死亡狀態(tài)

注意:一旦線程停止(死亡)了,就不能再次開啟任務(wù)

  • 多線程的安全隱患
a、資源共享
1>1塊資源可能會被多個線程共享,也就是“多個線程可能會同一時間訪問同一塊資源”。
2>比如多個線程訪問同一個對象、同一個變量、同一個文件。

b、當(dāng)多個線程訪問同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題。

  • 安全隱患分析


    thread-08.png
  • 安全隱患解決 – 互斥鎖


    thread-09.png
  • 互斥鎖:
    1.互斥鎖使用格式

為代碼添加同步鎖(互斥鎖)
/*
 * token:鎖對象 (要使用全局的對象) 建議直接使用self
 * {}   要加鎖的代碼段
 * 注意點:①加多把鎖死無效的;②要注意加鎖的位置
*/
@synchronized(token) { 
   // 需要鎖定的代碼  
}

注意:鎖定1份代碼只用1把鎖,用多把鎖是無效的。

2.互斥鎖的優(yōu)缺點

優(yōu)點:能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題。
缺點:需要消耗大量的CPU資源。

3.互斥鎖的使用前提

多條線程可能存在同一時間搶奪同一塊資源(即:多個線程同時訪問同一個變量)。

4.相關(guān)專業(yè)術(shù)語:線程同步

a>`線程同步的意思是`:多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務(wù))。
b>互斥鎖,就是使用了線程同步技術(shù)。
  • 售票案例
#import "ViewController.h"

@interface ViewController ()

@property (strong, nonatomic)NSThread *threadA;
@property (strong, nonatomic)NSThread *threadB;
@property (strong, nonatomic)NSThread *threadC;

//票總數(shù)
@property (assign, nonatomic)NSInteger totalCount;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化票總數(shù)
    self.totalCount = 100;
    
    self.threadA = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    
    self.threadB = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    
    self.threadC = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    
    self.threadA.name = @"售票員A";
    self.threadB.name = @"售票員B";
    self.threadC.name = @"售票員C";
   
    [self.threadA start];
    [self.threadB start];
    [self.threadC start];
    
}

- (void)saleTickets{
    
    while (1) {
        //為代碼添加同步鎖(互斥鎖)
         /*
          * token:鎖對象 (要使用全局的對象) 建議直接使用self
          * {} 要加鎖的代碼段
          * 注意點: ①加多把鎖死無效的   ②要注意加鎖的位置
          */
        //先加鎖
        @synchronized (self) {
            
            //先讀取余票數(shù)
            NSInteger count = self.totalCount;
            
            if (count>0) {
                
                //售出一張(重新寫入)
                self.totalCount = count - 1;
                
//                for (NSInteger i = 0; i < 100000000; i++) {
//
//                }

                NSLog(@"%@售出一張票,余票:%zd",[NSThread currentThread].name,self.totalCount);
                
            }else{
                
                NSLog(@"%@:票已經(jīng)售空",[NSThread currentThread].name);
                
                break;
            
            }
        }
    }
}

@end
  • 線程間通信
  • 什么叫做線程間通信?
    1、 在1個進程中,線程往往不是孤立存在的,多個線程之間需要經(jīng)常進行通信。

  • 線程間通信的體現(xiàn):
    1、1個線程傳遞數(shù)據(jù)給另1個線程。
    2、在1個線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另1個線程繼續(xù)執(zhí)行任務(wù)。

  • 線程間通信常用方法

  /* 參數(shù)說明
     *
     * 第一個參數(shù):方法選擇器  回到主線程要做什么(方法)
     * 第二個參數(shù):調(diào)用函數(shù)需要傳遞的參數(shù)
     * 第三個參數(shù):是否等待該方法執(zhí)行完畢才繼續(xù)往下執(zhí)行 YES:等待
     */
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
  • 線程間通信案例


    thread-10.png

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //創(chuàng)建子線程方式一
//    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downLoadImage) object:nil];
//    [thread start];
    
    //創(chuàng)建子線程方式二
    [NSThread detachNewThreadSelector:@selector(downLoadImage) toTarget:self withObject:nil];
    
    //創(chuàng)建子線程方式三(隱式創(chuàng)建線程)
//    [self performSelectorInBackground:@selector(downLoadImage) withObject:nil];
 
}
//執(zhí)行下載任務(wù)
- (void)downLoadImage
{
    NSLog(@"---%@---",[NSThread currentThread]);
    
    NSURL *url = [NSURL URLWithString:@"https://ns-strategy.cdn.bcebos.com/ns-strategy/upload/fc_big_pic/part-00654-2651.jpg"];
    
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    
    UIImage *image = [UIImage imageWithData:imageData];
      
    //回到主線程刷新UI界面
    /* 參數(shù)說明
       *
       * 第一個參數(shù):方法選擇器  回到主線程要做什么(方法)
       * 第二個參數(shù):調(diào)用函數(shù)需要傳遞的參數(shù)
       * 第三個參數(shù):是否等待該方法執(zhí)行完畢才繼續(xù)往下執(zhí)行 YES:等待
       */
    //方式一
//    [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
  
  //方式二
    [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
    //方式三
//    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
}
//回到主線程刷新UI界面
- (void)showImage:(UIImage *)image{
    NSLog(@"---%@---",[NSThread currentThread]);
    self.imageView.image = image;
}
@end
  • 原子和非原子屬性

1、OC在定義屬性時有nonatomicatomic兩種選擇
2、atomic:原子屬性,為setter方法加鎖(默認(rèn)就是atomic
3、nonatomic:非原子屬性,不會為setter方法加鎖

  • 原子和非原子屬性的選擇?

1、nonatomicatomic對比
2、atomic:線程安全,需要消耗大量的資源
3、nonatomic:非線程安全,適合內(nèi)存小的移動設(shè)備

  • iOS開發(fā)的建議

1、所有屬性都聲明為nonatomic
2、盡量避免多線程搶奪同一塊資源
3、盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動客戶端的壓力


GCD

  • 基本概念:

1、全稱是Grand Central Dispatch,可譯為牛逼的中樞調(diào)度器。
2、純C語言,提供了非常多強大的函數(shù)。

  • GCD的優(yōu)勢

1、GCD是蘋果公司為多核并行運算提出的解決方案
2、GCD會自動利用更多的CPU內(nèi)核(比如雙核、四核)
3、GCD會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程)
4、程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼

任務(wù)和隊列
  • GCD中有2個核心概念
    1、任務(wù):執(zhí)行什么操作。
    2、隊列:用來存放任務(wù)。

  • GCD的使用就2個步驟
    1、定制任務(wù):確定想做的事情。
    2、將任務(wù)添加到隊列中:(同步函數(shù)|異步函數(shù))
    a、GCD會自動將隊列中的任務(wù)取出,放到對應(yīng)的線程中執(zhí)行;
    b、任務(wù)的取出遵循隊列FIFO原則:先進先出,后進后出。

  • 封裝任務(wù)的函數(shù):

1、同步函數(shù)dispatch_sync
a.不具備開啟線程的能力,不能開線程
b.任務(wù)的執(zhí)行方式:同步執(zhí)行

2、異步函數(shù)dispatch_async
a.具備開啟線程的能力,能開線程
b.任務(wù)的執(zhí)行方式:異步執(zhí)行

執(zhí)行任務(wù)
  • GCD中有2個用來執(zhí)行任務(wù)的常用函數(shù)
  • 同步的方式執(zhí)行任務(wù)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:隊列
block:任務(wù)
  • 異步的方式執(zhí)行任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
  • 同步和異步的區(qū)別:(決定了是否具備開線程的能力)

同步:只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力

  • 隊列的類型:GCD的隊列可以分為2大類型:并發(fā)隊列、串行隊列

1.并發(fā)隊列(Concurrent Dispatch Queue)
a.可以讓多個任務(wù)并發(fā)(同時)執(zhí)行自動開啟多個線程同時執(zhí)行任務(wù)
b.并發(fā)功能只有在異步dispatch_async函數(shù)下才有效

2.串行隊列(Serial Dispatch Queue)
a.讓任務(wù)一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))

  • 隊列的創(chuàng)建
使用dispatch_queue_create函數(shù)創(chuàng)建隊列
/**
 *第一個參數(shù): 隊列名稱 
 *第一個參數(shù): 隊列的類型
 */
dispatch_queue_t queue = dispatch_queue_create(const char *label, // 隊列名稱 
dispatch_queue_attr_t attr); // 隊列的類型
  • 創(chuàng)建并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("queue_Name", DISPATCH_QUEUE_CONCURRENT);
  • GCD默認(rèn)已經(jīng)提供了全局的并發(fā)隊列,供整個應(yīng)用使用,可以無需手動創(chuàng)建
使用dispatch_get_global_queue函數(shù)獲得全局的并發(fā)隊列
/**
 *第一個參數(shù): 隊列的優(yōu)先級
 *第一個參數(shù): 此參數(shù)暫時無用,用0即可
 */
dispatch_queue_t queue = dispatch_get_global_queue(
dispatch_queue_priority_t priority, // 隊列的優(yōu)先級
unsigned long flags); // 此參數(shù)暫時無用,用0即可
  • 獲得全局并發(fā)隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
  • 全局并發(fā)隊列的優(yōu)先級
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(rèn)(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺
  • 創(chuàng)建串行隊列
使用dispatch_queue_create函數(shù)創(chuàng)建串行隊列

// 創(chuàng)建串行隊列(隊列類型傳遞NULL或者DISPATCH_QUEUE_SERIAL)

dispatch_queue_t queue = dispatch_queue_create("queue_Name", NULL); 

  • 使用主隊列(跟主線程相關(guān)聯(lián)的隊列)
    1.主隊列是GCD自帶的一種特殊串行隊列。
    2.放在主隊列中的任務(wù),都會放到主線程中執(zhí)行。
    3.使用dispatch_get_main_queue()獲得主隊列。
    dispatch_queue_t queue = dispatch_get_main_queue();。
  • 有4個術(shù)語比較容易混淆:同步、異步、并發(fā)、串行

1.同步異步主要影響:能不能開啟新的線程
同步:只是在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力

異步:可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力

2.并發(fā)串行主要影響:任務(wù)的執(zhí)行方式
并發(fā):允許多個任務(wù)并發(fā)(同時)執(zhí)行
串行一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù)

  • GCD 的基本使用
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

#pragma mark -----------------------
#pragma mark Events
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self syncConcurrent];
    //[self performSelectorInBackground:@selector(syncMain) withObject:nil];
}

#pragma mark -----------------------
#pragma mark GCD的基本使用(函數(shù) + 隊列)
//異步函數(shù) + 并發(fā)隊列:會開啟多條子線程,所有的任務(wù)并發(fā)執(zhí)行
//注意:開幾條線程并不是由任務(wù)的數(shù)量決定的,是有GCD內(nèi)部自動決定的
-(void)asyncConcurrent
{
    //01 創(chuàng)建隊列
    /* 參數(shù)說明
     *
     * 第一個參數(shù):C語言的字符串 給隊列起個名字(建議:com.DB001.www.DownloadQueue)
     * 第二個參數(shù):類型
     *          DISPATCH_QUEUE_CONCURRENT  并發(fā)隊列
     *          DISPATCH_QUEUE_SERIAL      串行隊列
     */
//創(chuàng)建并發(fā)隊列
    //dispatch_queue_t queue = dispatch_queue_create("com.DB001.www.DownloadQueue", DISPATCH_QUEUE_CONCURRENT);

//獲取全局并發(fā)隊列
    //DISPATCH_QUEUE_PRIORITY_DEFAULT == 0 默認(rèn)優(yōu)先級
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    
     NSLog(@"-----start----");
    
    //02 封裝任務(wù),把任務(wù)添加到隊列
    dispatch_async(queue, ^{
        NSLog(@"1-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"3-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"4-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"5-----%@",[NSThread currentThread]);
    });
    
    
    NSLog(@"-----end----");
    
    
}

//異步函數(shù) + 串行隊列:會開啟一條子線程,所有的任務(wù)在該子線程中串行執(zhí)行
-(void)asyncSerial
{
    //01 創(chuàng)建隊列
    /* 參數(shù)說明
     *
     * 第一個參數(shù):C語言的字符串 給隊列起個名字(建議:com.DB001.www.DownloadQueue)
     * 第二個參數(shù):類型
     *          DISPATCH_QUEUE_CONCURRENT  并發(fā)隊列
     *          DISPATCH_QUEUE_SERIAL      串行隊列
     */
    dispatch_queue_t queue = dispatch_queue_create("com.DB001.www.DownloadQueue", DISPATCH_QUEUE_SERIAL);
    
    
    //02 封裝任務(wù),把任務(wù)添加到隊列
    dispatch_async(queue, ^{
        NSLog(@"1-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"3-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"4-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"5-----%@",[NSThread currentThread]);
    });
    
    
}

//同步函數(shù) + 并發(fā)隊列:不會開啟子線程,所有的任務(wù)在當(dāng)前線程中串行執(zhí)行
-(void)syncConcurrent
{
    //01 創(chuàng)建隊列
    /* 參數(shù)說明
     *
     * 第一個參數(shù):C語言的字符串 給隊列起個名字(建議:com.DB001.www.DownloadQueue)
     * 第二個參數(shù):類型
     *          DISPATCH_QUEUE_CONCURRENT  并發(fā)隊列
     *          DISPATCH_QUEUE_SERIAL      串行隊列
     */
    dispatch_queue_t queue = dispatch_queue_create("com.DB001.www.DownloadQueue", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"-----start----");
    //02 封裝任務(wù),把任務(wù)添加到隊列
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"3-----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"4-----%@",[NSThread currentThread]);
    });
    
   NSLog(@"-----end----");
    
}

//同步函數(shù) + 串行隊列:不會開啟子線程,所有的任務(wù)在當(dāng)前線程中串行執(zhí)行
-(void)syncSerial
{
    //01 創(chuàng)建隊列
    /* 參數(shù)說明
     *
     * 第一個參數(shù):C語言的字符串 給隊列起個名字(建議:com.DB001.www.DownloadQueue)
     * 第二個參數(shù):類型
     *          DISPATCH_QUEUE_CONCURRENT  并發(fā)隊列
     *          DISPATCH_QUEUE_SERIAL      串行隊列
     */
    dispatch_queue_t queue = dispatch_queue_create("com.DB001.www.DownloadQueue", DISPATCH_QUEUE_SERIAL);
    
    
    //02 封裝任務(wù),把任務(wù)添加到隊列
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"3-----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"4-----%@",[NSThread currentThread]);
    });
    
}

//異步函數(shù) + 主隊列:不會開線程,所有的任務(wù)都在主線程中串行執(zhí)行
-(void)asyncMain
{
    //01 獲得主隊列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    //02 封裝任務(wù)并把任務(wù)添加到隊列
    dispatch_async(queue, ^{
        NSLog(@"1-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"3-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"4-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"5-----%@",[NSThread currentThread]);
    });

}

/**
  * 同步函數(shù) + 主隊列:
  * 這時會產(chǎn)生 -- “死鎖”問題
  * 產(chǎn)生死鎖的原因:是因為在主隊列中的任務(wù)必須在主線程中執(zhí)行,當(dāng)主隊列中有任務(wù)時,主隊列就會安排主線程來執(zhí)行任務(wù),但是在調(diào)度之前會先檢查主線程的狀態(tài)(是否在忙)如果在忙就暫停調(diào)度直到主線程空閑位置;又因為是同步的方式在執(zhí)行任務(wù),任務(wù)1執(zhí)行完之后才會執(zhí)行之后的任務(wù),這時主線程是繁忙的狀態(tài),會一直卡在執(zhí)行任務(wù)1之前,出現(xiàn)了相互等待的現(xiàn)象(即死鎖)
  * 解決“死鎖”問題: [self performSelectorInBackground:@selector(syncMain) withObject:nil];
  */
-(void)syncMain
{
    //01 獲得主隊列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    NSLog(@"--------");
    //02 封裝任務(wù)并把任務(wù)添加到隊列
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"3-----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"4-----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"5-----%@",[NSThread currentThread]);
    });
    
}

@end

任務(wù)的執(zhí)行方式:
(1) 同步執(zhí)行:必須等當(dāng)前任務(wù)執(zhí)行完畢,才能執(zhí)行后面的任務(wù)
(2 )異步執(zhí)行:不必須等當(dāng)前任務(wù)執(zhí)行完畢,就能執(zhí)行后面的任務(wù)

  • 各種組合情況:

同步函數(shù) + 串行隊列 不會開線程,所有的任務(wù)在當(dāng)前線程串行執(zhí)行
同步函數(shù) + 并發(fā)隊列 不會開線程,所有的任務(wù)在當(dāng)前線程串行執(zhí)行
同步函數(shù) + 主隊列 死鎖

異步函數(shù) + 串行隊列 會開1條線程,所有的任務(wù)在子線程中串行執(zhí)行;
異步函數(shù) + 并發(fā)隊列 會開N條線程,所有的任務(wù)在子線程中并發(fā)執(zhí)行 (?? 線程的數(shù)量!= 任務(wù)的數(shù)量);
異步函數(shù) + 主隊列 不會開線程,所有的任務(wù)在主線程串行執(zhí)行。

總結(jié):
開線程的情況:①異步函數(shù) ② 不能是主隊列
開幾條線程:看隊列,如果是并發(fā)隊列那么就N條,如果是串行隊列那么就開1條

  • 各種隊列的執(zhí)行效果
并發(fā)隊列 手動創(chuàng)建的串行隊列 主隊列
同步(sync) a.沒有開啟新線程
b.串行執(zhí)行任務(wù)
a.沒有開啟新線程
b.串行執(zhí)行任務(wù)
a.沒有開啟新線程
b.串行執(zhí)行任務(wù)
異步(async) a.開啟新線程
b.并發(fā)執(zhí)行任務(wù)
a.開啟新線程 (僅開1條)
b.串行執(zhí)行任務(wù)
a.沒有開啟新線程
b.串行執(zhí)行任務(wù)

注意
使用sync函數(shù)往當(dāng)前串行隊列中添加任務(wù),會卡住當(dāng)前的串行隊列

GCD的線程之間的通信
  • 從子線程回到主線程
//獲取全局并發(fā)隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
dispatch_async(queue, ^{
    // 執(zhí)行耗時的異步操作...

      dispatch_async(dispatch_get_main_queue(), ^{
        // 回到主線程,執(zhí)行UI刷新操作

        });
});

GCD的其他函數(shù):

(1) 一次性代碼 dispatch_once
(2) 延時執(zhí)行 dispatch_after
(3) 快速迭代 dispatch_apply
(4) 柵欄函數(shù) dispatch_barrier_async

  • (1) 一次性代碼: dispatch_once
  • 整個程序運行過程中只會執(zhí)行一次 + 本身是線程安全
  • 應(yīng)用:單例模式

使用dispatch_once函數(shù)能保證某段代碼在程序運行過程中只被執(zhí)行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
});

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self once];
}

-(void)once
{
    static dispatch_once_t onceToken;
    NSLog(@"++++++%zd",onceToken);
    內(nèi)部實現(xiàn)原理:判斷onceToken的值 == 0 來決定是否執(zhí)行block中的任務(wù)
    dispatch_once(&onceToken, ^{
        NSLog(@"once------");
    });
}
  • (2) 延時執(zhí)行:dispatch_after
  • iOS常見的延時執(zhí)行有三種方法:

1、調(diào)用NSObject的方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后再調(diào)用self的run方法

2、使用GCD函數(shù)
dispatch_queue_t queue = dispatch_get_main_queue()“主隊列”
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);“全局并發(fā)隊列”
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
// 2秒后執(zhí)行這里的代碼...
});

3、使用NSTimer
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self delay];
}
-(void)delay
{
    NSLog(@"--delay-");
    //延遲執(zhí)行
//    [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
    
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
    
    //GCD中的延遲執(zhí)行
    /* 參數(shù)說明
     *
     * 第一個參數(shù):設(shè)置時間(GCD中的時間單位是納秒)
     * 第二個參數(shù):隊列(決定block中的任務(wù)在哪個線程中執(zhí)行,如果是主隊列就是主線程,否在就在子線程)
     * 第三個參數(shù):設(shè)置任務(wù)
     * 原理:(哪個簡單)
     * A 先把任務(wù)提交到隊列,然后等兩秒再執(zhí)行 錯誤
     * B 先等兩秒,再把任務(wù)提交到隊列        正確
     */
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
//        NSLog(@"-----GCD------%@",[NSThread currentThread]);
//    });
}

-(void)run
{
    NSLog(@"run--%@",[NSThread currentThread]);
}
  • (3) 快速迭代: dispatch_apply

使用dispatch_apply函數(shù)能進行快速迭代遍歷
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){
// 執(zhí)行10次代碼,index順序不確定
});

//快速迭代(遍歷)
-(void)apply
{
    //在當(dāng)前線程中串行執(zhí)行(提高效率)
    //1000  1   ==>10天
    //1000  10  ==>1天
    for (int i = 0; i < 10; ++i) {
        NSLog(@"%zd----%@",i,[NSThread currentThread]);
    }
    NSLog(@"____________");
    /* 參數(shù)說明
     *
     * 第一個參數(shù):遍歷的次數(shù)
     * 第二個參數(shù):隊列
     */
    //并發(fā)隊列:會開啟多條子線程和主線程一起并發(fā)的執(zhí)行任務(wù)
    //主隊列:死鎖
    //普通的串行的隊列:和for循環(huán)一樣

    dispatch_queue_t queue = dispatch_queue_create("DownloadQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_apply(10, queue, ^(size_t i) {
       
        NSLog(@"%zd----%@",i,[NSThread currentThread]);
    });
    
}

快速迭代的案例:將from文件夾中的文件剪切到to文件夾中
-(void)test2
{
    //01 獲得文件夾的路徑
    NSString *from = @"/Users/xiaomage/Desktop/from";
    NSString *to = @"/Users/xiaomage/Desktop/to";
    
    //02 獲得該文件夾下面的所有文件
    NSArray *subPaths = [[NSFileManager defaultManager] subpathsAtPath:from];
    NSLog(@"%@",subPaths);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_apply(subPaths.count, queue , ^(size_t i) {
        
        NSString *fileName = subPaths[i];
        
        //拼接文件的全路徑
        NSString *fromFullPath = [from stringByAppendingPathComponent:fileName];
        NSString *toFullPath = [to stringByAppendingPathComponent:fileName];
        
        //執(zhí)行文件的剪切操作
        [[NSFileManager defaultManager] moveItemAtPath:fromFullPath toPath:toFullPath error:nil];
        
        NSLog(@"%@--%@--%@",fromFullPath,toFullPath,[NSThread currentThread]);
        
    });
}
  • (4) 柵欄函數(shù): dispatch_barrier_async

在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完成之后才會執(zhí)行
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
?? 注意:這個queue不能是全局的并發(fā)隊列

-(void)barrier
{
    //需求:有4個任務(wù),要求開啟多條線程來執(zhí)行這些任務(wù)
    //增加需求:新的任務(wù)++++++,要求在12執(zhí)行之后執(zhí)行,要保證該任務(wù)執(zhí)行完之后才能執(zhí)行后面的34任務(wù)
    
    //柵欄函數(shù):前面的任務(wù)并發(fā)執(zhí)行,后面的任務(wù)也是并發(fā)執(zhí)行
    //當(dāng)前面的任務(wù)執(zhí)行完畢之后,執(zhí)行柵欄函數(shù)中的任務(wù),等該任務(wù)執(zhí)行完畢后再執(zhí)行后面的任務(wù)
    //?? 不能使用全局并發(fā)隊列
    
    //01 獲得隊列
    dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT);
    //dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    //02 封裝任務(wù),并且添加到隊列
    dispatch_async(queue, ^{
        NSLog(@"1-----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"2-----%@",[NSThread currentThread]);
    });
    
    //柵欄函數(shù)
    dispatch_barrier_async(queue, ^{
        NSLog(@"+++++++++++");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"3-----%@",[NSThread currentThread]);
    });
    
    
    dispatch_async(queue, ^{
        NSLog(@"4-----%@",[NSThread currentThread]);
    });
    
    
}
GCD的隊列組:
  • 基本概念:隊列組是用來管理隊列中任務(wù)的執(zhí)行。
  • 使用步驟:
  • 創(chuàng)建隊列組: dispatch_group_create();
  • 創(chuàng)建隊列:dispatch_queue_create(param1 , parame2);
  • 使用隊列組異步函數(shù)來封裝任務(wù), 然后提交到隊列中dispatch_group_async(group, queue, ^{
    });
  • 把當(dāng)前所有任務(wù)執(zhí)行的情況, 都納入到隊列組監(jiān)聽的范圍中
  • 異步函數(shù)dispatch_async(queue, ^{ })和隊列組dispatch_group_async(group, queue, ^{})的區(qū)別:

(1) dispatch_group_async(group, queue, ^{ });
1、 封裝任務(wù)
2 、把任務(wù)添加到隊列
3 、監(jiān)聽任務(wù)的執(zhí)行情況
(2) dispatch_async(queue, ^{ });
1 、封裝任務(wù)
2 、把任務(wù)添加到隊列

  • 隊列組的簡單使用:監(jiān)聽任務(wù)的完成

1、所有的任務(wù)會并發(fā)的執(zhí)行(不按序)。
2、所有的異步函數(shù), 都是添加到隊列中, 然后再納入到隊列組的監(jiān)聽范圍。
3、使用dispatch_group_notify(隊列組, 隊列)函數(shù), 來監(jiān)聽在這個函數(shù)上面的任務(wù)執(zhí)行是否完成, 當(dāng)任務(wù)完成, 就會調(diào)用這個方法。

  • 案例需求:

需求1:4個任務(wù),在子線程中并發(fā)執(zhí)行,添加任務(wù)"+++++",必須等所有的任務(wù)都執(zhí)行完再執(zhí)行。

-(void)group{
1. 創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
    
2. 創(chuàng)建并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT);

3.封裝任務(wù),添加到隊列并監(jiān)聽任務(wù)的執(zhí)行情況
 dispatch_group_async(group, queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
 });
    
dispatch_group_async(group, queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
});
    
dispatch_group_async(group, queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
});

dispatch_group_async(group, queue, ^{
        NSLog(@"4---%@",[NSThread currentThread]);
});
4.攔截通知,當(dāng)所有的任務(wù)都執(zhí)行完畢后,執(zhí)行++++操作
dispatch_group_notify(group, queue, ^{
        NSLog(@"++++++++%@",[NSThread currentThread]);
});
}

需求2:攔截多個隊列中任務(wù)。

-(void)group{
1. 創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
    
2. 創(chuàng)建并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue1 = dispatch_queue_create("Test1", DISPATCH_QUEUE_CONCURRENT);

3.封裝任務(wù),添加到隊列并監(jiān)聽任務(wù)的執(zhí)行情況
 dispatch_group_async(group, queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);
 });
    
dispatch_group_async(group, queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
});
    
dispatch_group_async(group, queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
});

dispatch_group_async(group, queue1, ^{
        NSLog(@"4---%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue1, ^{
        NSLog(@"5---%@",[NSThread currentThread]);
});

4.攔截通知,當(dāng)所有的任務(wù)都執(zhí)行完畢后,執(zhí)行++++操作
dispatch_group_notify(group, queue, ^{
        NSLog(@"++++++++%@",[NSThread currentThread]);
});
}

探討:
dispatch_group_notify內(nèi)部是異步的執(zhí)行
dispatch_group_notify隊列:決定該block在哪個線程中處理(主:主線程 非主隊列:子線程)
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"++++++++%@",[NSThread currentThread]);
});

  • 使用隊列組的小實例 --- 下載兩張圖片, 然后合成后展示
- (void)downLoadTwoPicturesThenComposeOnePicture{
    //需求:開子線程下載兩張圖片,合成圖片,顯示出來
    //01 創(chuàng)建隊列組
    dispatch_group_t group = dispatch_group_create();
    
    //02 獲得并發(fā)隊列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    //03 下載圖片1
    dispatch_group_async(group, queue, ^{
        
        //001 url
        NSURL *URL = [NSURL URLWithString:@"http://pridelands.ru/pictures/real/puma/Cougar_018.jpg"];
        
        //002 Data
        NSData *imageData = [NSData dataWithContentsOfURL:URL];
        
        //003 轉(zhuǎn)換
        self.image1 = [UIImage imageWithData:imageData];
        
          NSLog(@"Download1-----%@",[NSThread currentThread]);
    });
    
    //04 下載圖片2
    dispatch_group_async(group, queue, ^{
        
        //001 url
        NSURL *URL = [NSURL URLWithString:@"http://m2.quanjing.com/2m/pust017/1574r-012067.jpg"];
        
        //002 Data
        NSData *imageData = [NSData dataWithContentsOfURL:URL];
        
        //003 轉(zhuǎn)換
        self.image2 = [UIImage imageWithData:imageData];
        
          NSLog(@"Download2-----%@",[NSThread currentThread]);
        
    });
    
    //05 攔截通知,合成圖片
    dispatch_group_notify(group, queue, ^{
        
        //001 開啟上下文
        UIGraphicsBeginImageContext(CGSizeMake(300, 300));
        
        //002 畫圖1,2
        [self.image1 drawInRect:CGRectMake(0, 0, 150, 300)];
        [self.image2 drawInRect:CGRectMake(150, 0, 150, 300)];
        
        NSLog(@"Combie-----%@",[NSThread currentThread]);
        //003 根據(jù)上下文得到圖片
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        
        //004 關(guān)閉上下文
        UIGraphicsEndImageContext();
        
        //06 顯示圖片(線程間通信)
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            NSLog(@"UI-----%@",[NSThread currentThread]);
        });
    });
    
}

NSOperation 與 NSOperationQueue
  • NSOperation的作用

配合使用 NSOperationNSOperationQueue 也能實現(xiàn)多線程編程。

  • NSOperationNSOperationQueue實現(xiàn)多線程的具體步驟:
  • 先將需要執(zhí)行的操作封裝到一個NSOperation對象中
  • 然后將NSOperation對象添加到NSOperationQueue
  • 系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來
  • 將取出的NSOperation封裝的操作放到一條新線程中執(zhí)行
  • NSOperation的子類

  • NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類。

  • 使用 NSOperation 子類有3種:

  • NSInvocationOperation
  • NSBlockOperation
  • 自定義子類繼承 NSOperation ,實現(xiàn)內(nèi)部相應(yīng)的方法
  • NSInvocationOperation
  • 創(chuàng)建NSInvocationOperation對象.
    -(id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
  • 調(diào)用start方法開始執(zhí)行操作.
    -(void)start;
    一旦執(zhí)行操作,就會調(diào)用target的sel方法.

注意:
默認(rèn)情況下,調(diào)用了start方法后并不會開一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作。
只有將NSOperation放到一個NSOperationQueue中,才會異步執(zhí)行操作

-(void)invocationOperation
{
    //01 封裝操作對象
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
    
    //02 執(zhí)行操作
     [op1 start];
}
  • NSBlockOperation
  • 創(chuàng)建NSBlockOperation對象
    +(id)blockOperationWithBlock:(void (^)(void))block;
-(void)blockOperation
{
    //操作:NSBlockOperation對象
    //任務(wù):block
    
    //01 封裝操作對象
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];

    //02 執(zhí)行操作
    [op1 start];
}
  • 通過addExecutionBlock:方法添加更多的操作
    - (void)addExecutionBlock:(void (^)(void))block;
    注意:只要NSBlockOperation封裝的操作數(shù) > 1,就會異步執(zhí)行操作.
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    
    //追加任務(wù)
    //當(dāng)一個操作中的任務(wù)數(shù)量>1的時候,就會開啟子線程和當(dāng)前線程一起執(zhí)行任務(wù)
    [op3 addExecutionBlock:^{
        NSLog(@"4----%@",[NSThread currentThread]);
    }];
    
    [op3 addExecutionBlock:^{
        NSLog(@"5----%@",[NSThread currentThread]);
    }];

  [op3 start];
  • NSOperationQueue
  • NSOperationQueue的作用.
  • NSOperation可以調(diào)用start方法來執(zhí)行任務(wù),但默認(rèn)是同步執(zhí)行的.
  • 如果將NSOperation添加到NSOperationQueue(操作隊列)中,系統(tǒng)會自動異步執(zhí)行NSOperation中的操作.
  • 添加操作到NSOperationQueue中
    -(void)addOperation:(NSOperation *)op;
    -(void)addOperationWithBlock:(void (^)(void))block;
  • 操作隊列的基本使用(操作 + 隊列)
-(void)invocationOperationWithQueue
{
    //01 創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02 封裝操作
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
    
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download2) object:nil];
    
    NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download3) object:nil];
    
    //03 把操作添加到隊列
    [queue addOperation:op1];  
    [queue addOperation:op2];
    [queue addOperation:op3];
    
}

注意:

  • 開啟幾條子線程并不是由操作的數(shù)量決定的
    -[queue addOperation:op1];該方法內(nèi)部會自動的調(diào)用start方法執(zhí)行任務(wù)
-(void)blockOperationWithQueue
{
    //01 創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02 封裝操作對象
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    
    //03 把操作添加到隊列中
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    
    //簡便方法:該方法內(nèi)部首先會把block中的任務(wù)封裝成一個操作(Operation),然后把該操作直接添加到隊列
    [queue addOperationWithBlock:^{
        NSLog(@"4----%@",[NSThread currentThread]);
    }];
    
}
  • 簡便方法:該方法內(nèi)部首先會把block中的任務(wù)封裝成一個操作(Operation),然后把該操作直接添加到隊列
    [queue addOperationWithBlock:^{
    NSLog(@"4----%@",[NSThread currentThread]);
    }];
  • 最大并發(fā)數(shù):同時執(zhí)行的任務(wù)數(shù)

最大并發(fā)數(shù)的相關(guān)方法
-(NSInteger)maxConcurrentOperationCount;
-(void)setMaxConcurrentOperationCount:(NSInteger)cnt;

案例:讓多個操作在子線程中順序執(zhí)行

把隊列的最大并發(fā)數(shù)設(shè)置為 1 ,就可以達到多個操作在子線程中順序執(zhí)行的效果。

-(void)changeSerialQueue
{
    //01 創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02 封裝操作對象
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"4----%@",[NSThread currentThread]);
    }];
    
    
    NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"5----%@",[NSThread currentThread]);
    }];
    
    //設(shè)置最大并發(fā)數(shù)對于任務(wù)數(shù)量大于1的操作是無效的
    //當(dāng)操作中任務(wù)數(shù)量>1的時候,會開啟多條子線程和當(dāng)前線程一起工作
//    [op5 addExecutionBlock:^{
//        NSLog(@"6----%@",[NSThread currentThread]);
//    }];
//    
//    [op5 addExecutionBlock:^{
//        NSLog(@"7----%@",[NSThread currentThread]);
//    }];
    
    //設(shè)置最大并發(fā)數(shù) == 同一時間最多有多少條線程在執(zhí)行
    //maxConcurrentOperationCount == 0 不能執(zhí)行任務(wù)
    //NSOperationQueueDefaultMaxConcurrentOperationCount = -1 -1指的是一個最大的值(表示不受限制)
    queue.maxConcurrentOperationCount = 1;
    
    //03 把操作添加到隊列中
//    [queue addOperation:op1];
//    [queue addOperation:op2];
//    [queue addOperation:op3];
//    [queue addOperation:op4];
//    [queue addOperation:op5];
    

    [queue addOperations:@[op1,op2,op3,op4,op5] waitUntilFinished:YES];
    
    NSLog(@"------");
    
}
  • 注意點:設(shè)置最大并發(fā)數(shù)對于任務(wù)數(shù)量大于1的操作是無效的
    創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    封裝操作    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"4----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"5----%@",[NSThread currentThread]);
    }];
    
   追加操作
   [op5 addExecutionBlock:^{
        NSLog(@"6----%@",[NSThread currentThread]);
    }];

   [op5 addExecutionBlock:^{
        NSLog(@"7----%@",[NSThread currentThread]);
   }];

   設(shè)置最大并發(fā)數(shù)為1
  queue.maxConcurrentOperationCount = 1;
  
  將操作添加到隊列執(zhí)行任務(wù)
  [queue addOperation:op4];
  [queue addOperation:op5];

  另一種添加操作到隊列的方法
  [queue addOperations:@[op4,op5] waitUntilFinished:YES];

   NSLog(@"--------");

這時的執(zhí)行順序?qū)τ?op5 是無效的
  • 注意點:
    1.maxConcurrentOperationCount == 0不能執(zhí)行任務(wù)。
    2.默認(rèn)NSOperationQueueDefaultMaxConcurrentOperationCount = -1 -1指的是一個最大的值(表示不受限制)。
    3.最大并發(fā)數(shù) 不一定 等于所開的線程數(shù):所開多少條線程是由系統(tǒng)決定的。
    4.[queue addOperations:@[op4,op5] waitUntilFinished:YES];這是另一種添加操作到隊列的方式,后面參數(shù):YES時,必須執(zhí)行完隊列中的操作,才能執(zhí)行之后的程序;NO時,可以不用執(zhí)行完隊列中的操作就可以執(zhí)行程序。
  • 隊列的取消、暫停、恢復(fù)
  • 取消隊列的所有操作
  • -(void)cancelAllOperations;
  • 提示:也可以調(diào)用NSOperation- (void)cancel方法取消單個操作
  • 暫停和恢復(fù)隊列
  • -(void)setSuspended:(BOOL)b; // YES代表暫停隊列,NO代表恢復(fù)隊列
    -> -(BOOL)isSuspended;
- (void)start{
    //01 創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02 封裝操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5000; ++i) {
            NSLog(@"1--%zd--%@",i,[NSThread currentThread]);
        }
        
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5000; ++i) {
            NSLog(@"2--%zd--%@",i,[NSThread currentThread]);
        }
    }];
    
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5000; ++i) {
            NSLog(@"3--%zd--%@",i,[NSThread currentThread]);
        }
    }];
    
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 5000; ++i) {
            NSLog(@"4--%zd--%@",i,[NSThread currentThread]);
        }
    }];
    
    //設(shè)置最大并發(fā)數(shù)
    queue.maxConcurrentOperationCount = 1;
    
    //03 添加到隊列
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];
    
    self.queue = queue;
}

- (IBAction)suspendBtnClick:(id)sender {
    
    //暫停 YES
    //只能暫停當(dāng)前操作后面的操作,當(dāng)前操作不可分割必須執(zhí)行完畢
    //操作是有狀態(tài)的
    [self.queue setSuspended:YES];
}
- (IBAction)resumeBtnClick:(id)sender {
    
    //恢復(fù)
    [self.queue setSuspended:NO];
}
- (IBAction)cancelBtnClick:(id)sender {
    
    //取消 取消所有的操作
    //只能取消隊列中處理等待狀態(tài)的操作
    [self.queue cancelAllOperations];
}
自定義操作、自定義線程
  • 自定義NSOperation的步驟很簡單
  • 重寫- (void)main方法,在里面實現(xiàn)想執(zhí)行的任務(wù)
  • 重寫- (void)main方法的注意點
  • 自己創(chuàng)建自動釋放池(因為如果是異步操作,無法訪問主線程的自動釋放>池)
  • 經(jīng)常通過- (BOOL)isCancelled方法檢測操作是否被取消,對取消做出響應(yīng)
#import "ViewController.h"
#import "KXOperation.h"
#import "KXGThread.h"

@interface ViewController ()

@property (nonatomic, strong) NSOperationQueue *queue;
@end

@implementation ViewController

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self test2];
}

-(void)thread
{
    //自定義線程
    XMGThread *thread = [[XMGThread alloc]init];
    [thread start];
}

- (IBAction)startBtnClick:(id)sender {
    
//    [self test1];
    [self test2];
    
}
- (IBAction)suspendBtnClick:(id)sender {
    
    //暫停 YES
    //只能暫停當(dāng)前操作后面的操作,當(dāng)前操作不可分割必須執(zhí)行完畢
    //操作是有狀態(tài)的
    [self.queue setSuspended:YES];
}
- (IBAction)resumeBtnClick:(id)sender {
    
    //恢復(fù)
    [self.queue setSuspended:NO];
}
- (IBAction)cancelBtnClick:(id)sender {
    
    //取消 取消所有的操作
    //只能取消隊列中處理等待狀態(tài)的操作
    [self.queue cancelAllOperations];
}

-(void)test2
{
    //01 創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02 封裝操作(執(zhí)行任務(wù))
    XMGOperation *op = [[XMGOperation alloc]init];
    
    //03 把操作添加到隊列
    [queue addOperation:op];   //內(nèi)部會調(diào)用start方法 ->main
    
    //自定義操作的好處:代碼復(fù)用
    self.queue = queue;
}
@end

-------------------------------------------------------------------
#import "KXGOperation.h"

@implementation KXGOperation

//重寫內(nèi)部的main方法類告訴自定義的操作任務(wù)是什么?
-(void)main
{
    // NSLog(@"main----%@",[NSThread currentThread]);
    for (int i = 0; i < 10000; ++i) {
        NSLog(@"1---%zd--%@",i,[NSThread currentThread]);
    }
    
    //官方建議:在自定義操作的時候每執(zhí)行完一個耗時操作就判斷一下當(dāng)前操作是否被取消,如果被取消就直接返回
    if (self.isCancelled) {
        return;
    }
    
    NSLog(@"++++++++++++++++");
    
    for (int i = 0; i < 10000; ++i) {
        NSLog(@"2---%zd--%@",i,[NSThread currentThread]);
    }
    
    if (self.isCancelled) {
        return;
    }
     NSLog(@"++++++++++++++++");
    
    for (int i = 0; i < 10000; ++i) {
        NSLog(@"3d---%zd--%@",i,[NSThread currentThread]);
    }
    
}
@end

-------------------------------------------------------------------
#import "KXGThread.h"

@implementation KXGThread

-(void)main
{
    NSLog(@"MAIN----%@",[NSThread currentThread]);
}
@end

操作依賴
  • NSOperation之間可以設(shè)置依賴來保證執(zhí)行順序

比如一定要讓操作A執(zhí)行完后,才能執(zhí)行操作B,可以這么寫。
[operationB addDependency:operationA]; // 操作B依賴于操作A。

  • 可以在不同queueNSOperation之間創(chuàng)建依賴關(guān)系。
    operation.png

注意:不能相互依賴
比如:A依賴B,B依賴A

操作的監(jiān)聽
  • 可以監(jiān)聽一個操作的執(zhí)行完畢
    -(void (^)(void))completionBlock;
    -(void)setCompletionBlock:(void (^)(void))block;
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //01 創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    NSOperationQueue *queue2 = [[NSOperationQueue alloc]init];
    
    //02 封裝操作對象
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"4----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"5----%@",[NSThread currentThread]);
    }];
    
    //監(jiān)聽任務(wù)執(zhí)行完畢
    op4.completionBlock = ^{
        
        NSLog(@"小4任務(wù)已完成");
    };
    
    [op1 setCompletionBlock:^{
         NSLog(@"老幺任務(wù)已完成");
    }];
    
    //03 設(shè)置操作依賴:4->3->2->1->5
    //?? 不能設(shè)置循環(huán)依賴,結(jié)果就是兩個任務(wù)都不會執(zhí)行
    [op5 addDependency:op1];
    [op1 addDependency:op2];
    //[op2 addDependency:op1];
    [op2 addDependency:op3];
    [op3 addDependency:op4];
    
    //04 把操作添加到隊列
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue addOperation:op4];
    [queue2 addOperation:op5];
    
    
}
線程間的通信
01 創(chuàng)建隊列
NSOperationQueue *queue  =[[NSOperationQueue alloc]init];

02封裝操作
NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{

            執(zhí)行任務(wù)
       [[NSOperationQueue mainQueue] addOperationWithBlock:^{

              回到主線程           

       }];

 }];

03 把操作添加到隊列
[queue addOperation:download];

  • GCD和NSOperation的對比:
1)GCD是純C語言的API,而操作隊列則是Object-C的對象。
2)在GCD中,任務(wù)用塊(block)來表示,而塊是個輕量級的數(shù)據(jù)結(jié)構(gòu);
   相反操作隊列中的『操作』NSOperation則是個更加重量級的Object-C對象。
3)具體該使用GCD還是使用NSOperation需要看具體的情況

 
NSOperation和NSOperationQueue的好處有:
1)NSOperationQueue可以方便的調(diào)用cancel方法來取消某個操作,而GCD中的任務(wù)是無法被取消的(安排好任務(wù)之后就不管了)。
2)NSOperation可以方便的指定操作間的依賴關(guān)系。
3)NSOperation可以通過KVO提供對NSOperation對象的精細控制(如監(jiān)聽當(dāng)前操作是否被取消或是否已經(jīng)完成等)
4)NSOperation可以方便的指定操作優(yōu)先級。操作優(yōu)先級表示此操作與隊列中其它操作之間的優(yōu)先關(guān)系,優(yōu)先級高的操作先執(zhí)行,優(yōu)先級低的后執(zhí)行。
5)通過自定義NSOperation的子類可以實現(xiàn)操作重用.
  • 使用Crearte函數(shù)創(chuàng)建的并發(fā)隊列和全局并發(fā)隊列的主要區(qū)別:
1)全局并發(fā)隊列在整個應(yīng)用程序中本身是默認(rèn)存在的并且對應(yīng)有高優(yōu)先級、默認(rèn)優(yōu)先級、低優(yōu)先級和后臺優(yōu)先級一共四個并發(fā)隊列,我們只是選擇其中的一個直接拿來用。而Create函數(shù)是實打?qū)嵉膹念^開始去創(chuàng)建一個隊列。
 
2)在iOS6.0之前,在GCD中凡是使用了帶Create和retain的函數(shù)在最后都需要做一次release操作。而主隊列和全局并發(fā)隊列不需要我們手動release。當(dāng)然了,在iOS6.0之后GCD已經(jīng)被納入到了ARC的內(nèi)存管理范疇中,即便是使用retain或者create函數(shù)創(chuàng)建的對象也不再需要開發(fā)人員手動釋放,我們像對待普通OC對象一樣對待GCD就OK。
 
3)在使用柵欄函數(shù)的時候,蘋果官方明確規(guī)定柵欄函數(shù)只有在和使用create函數(shù)自己的創(chuàng)建的并發(fā)隊列一起使用的時候才有效(沒有給出具體原因)
4)其它區(qū)別涉及到XNU內(nèi)核的系統(tǒng)級線程編程,不一一列舉。

多線程相關(guān)知識希望給朋友們帶來一丟丟的幫助,如有問題敬請批正?。?!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容