沒(méi)錯(cuò)!多線程全部在這里了!

相關(guān)代碼:
https://pan.baidu.com/s/1nvNdedz
https://pan.baidu.com/s/1qYF5vRI
請(qǐng)配合代碼閱讀,文章內(nèi)容來(lái)源于小馬哥視頻

Tips: 作者會(huì)不定期更新類似總結(jié)性的文章,如果你喜歡,請(qǐng)關(guān)注我。

多線程中的基本概念


1. 進(jìn)程

進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序。每個(gè)進(jìn)程之間是獨(dú)立的,每個(gè)進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存空間內(nèi)。
</br>

2. 線程

  • 基本概念
  • 1個(gè)進(jìn)程要想執(zhí)行任務(wù),必須得有線程(每1個(gè)進(jìn)程至少要有1條線程),線程是進(jìn)程的基本執(zhí)行單元,一個(gè)進(jìn)程(程序)的所有任務(wù)都在線程中執(zhí)行。
    • 線程的串行
  • 1個(gè)線程中任務(wù)的執(zhí)行是串行的,如果要在1個(gè)線程中執(zhí)行多個(gè)任務(wù),那么只能一個(gè)一個(gè)地按順序執(zhí)行這些任務(wù)。也就是說(shuō),在同一時(shí)間內(nèi),1個(gè)線程只能執(zhí)行1個(gè)任務(wù)。
    </br>

3. 多線程

  • 基本概念
    • 即1個(gè)進(jìn)程中可以開(kāi)啟多條線程,每條線程可以并行(同時(shí))執(zhí)行不同的任務(wù)。
  • 線程的并行
    • 并行即同時(shí)執(zhí)行。比如同時(shí)開(kāi)啟3條線程分別下載3個(gè)文件(分別是文件A、文件B、文件C)。
  • 多線程并發(fā)執(zhí)行的原理
    • 在同一時(shí)間里,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)。多線程并發(fā)(同時(shí))執(zhí)行,其實(shí)是CPU快速地在多條線程之間調(diào)度(切換),如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象。
  • 多線程優(yōu)缺點(diǎn)
    • 優(yōu)點(diǎn)
      • 能適當(dāng)提高程序的執(zhí)行效率。
      • 能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
    • 缺點(diǎn)
      • 開(kāi)啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用1M,子線程占用512KB),如果開(kāi)啟大量的線程,會(huì)占用大量的內(nèi)存空間,降低程序的性能。
      • 線程越多,CPU在調(diào)度線程上的開(kāi)銷就越大。
      • 程序設(shè)計(jì)更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享
        </br>

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

  • 主線程
  • 一個(gè)iOS程序運(yùn)行后,默認(rèn)會(huì)開(kāi)啟1條線程,稱為“主線程”或“UI線程”。
  • 作用,刷新顯示UI,處理UI事件
  • 使用注意
  • 不要將耗時(shí)操作放到主線程中去處理,會(huì)卡住線程。
  • 和UI相關(guān)的刷新操作必須放到主線程中進(jìn)行處理
    </br>

5. iOS中多線程的實(shí)現(xiàn)方案

  • pthread

  • 特點(diǎn):

    • 一套通用的多線程API
    • 適用于Unix\Linux\Windows等系統(tǒng)
    • 跨平臺(tái)\可移植
    • 使用難度大 b.使用語(yǔ)言:c語(yǔ)言 c.使用頻率:幾乎不用 d.線程生命周期:由程序員進(jìn)行管理
  • NSThread

    • 特點(diǎn):
      • 使用更加面向?qū)ο?/li>
      • 簡(jiǎn)單易用,可直接操作線程對(duì)象
    • 使用語(yǔ)言:OC語(yǔ)言
    • 使用頻率:偶爾使用
    • 線程生命周期:由程序員進(jìn)行管理
  • GCD

    • 特點(diǎn):
      • 旨在替代NSThread等線程技術(shù)
      • 充分利用設(shè)備的多核(自動(dòng))
    • 使用語(yǔ)言:C語(yǔ)言
    • 使用頻率:經(jīng)常使用
    • 線程生命周期:自動(dòng)管理
  • NSOperation

    • 特點(diǎn):
      • 基于GCD(底層是GCD)
      • 比GCD多了一些更簡(jiǎn)單實(shí)用的功能
      • 使用更加面向?qū)ο?/li>
    • 使用語(yǔ)言:OC語(yǔ)言
    • 使用頻率:經(jīng)常使用
    • 線程生命周期:自動(dòng)管理
      </br>

pthread[了解,主要是為了能看得懂三方庫(kù)的代碼]


#import <pthread.h>
   
//使用pthread創(chuàng)建線程對(duì)象 
pthread_t thread;
NSString *name = @"wendingding"; 

//使用pthread創(chuàng)建線程 

//第一個(gè)參數(shù):線程對(duì)象地址
//第二個(gè)參數(shù):線程屬性
//第三個(gè)參數(shù):指向函數(shù)的指針 
//第四個(gè)參數(shù):傳遞給該函數(shù)的參數(shù) 
pthread_create(&thread, NULL, run, (__bridge void *)(name));

</br>

NSThread


1. 基本用法

/*
  第一種創(chuàng)建線程的方式:alloc init.
  特點(diǎn):需要手動(dòng)開(kāi)啟線程,可以拿到線程對(duì)象進(jìn)行詳細(xì)設(shè)置 
  
  創(chuàng)建線程 
  第一個(gè)參數(shù):目標(biāo)對(duì)象 
  第二個(gè)參數(shù):選擇器,線程啟動(dòng)要調(diào)用哪個(gè)方法 
  第三個(gè)參數(shù):前面方法要接收的參數(shù)(最多只能接收一個(gè)參數(shù),沒(méi)有則傳nil)
 **/
 
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"wendingding"]; 

//設(shè)置屬性
threadA.name = @"線程A";
//設(shè)置優(yōu)先級(jí)  取值范圍 0.0 ~ 1.0 之間 最高是1.0 默認(rèn)優(yōu)先級(jí)是0.5 優(yōu)先級(jí)越高,執(zhí)行的次數(shù)(可能性)越高
threadA.threadPriority = 1.0;
/**
啟動(dòng)線程
!!![線程處于就緒狀態(tài),所有被標(biāo)記為start的線程會(huì)被維護(hù)在一個(gè)池中,
這樣系統(tǒng)就會(huì)知道這個(gè)池中的所有線程都是需要被調(diào)用的。
然后CPU快速地在多條線程之間調(diào)度(切換),造成了多線程并發(fā)執(zhí)行的假象。]
*/
[thread start];

/*
  第二種創(chuàng)建線程的方式:分離出一條子線程
  特點(diǎn):自動(dòng)啟動(dòng)線程,無(wú)法對(duì)線程進(jìn)行更詳細(xì)的設(shè)置 

  第一個(gè)參數(shù):線程啟動(dòng)調(diào)用的方法
  第二個(gè)參數(shù):目標(biāo)對(duì)象
  第三個(gè)參數(shù):傳遞給調(diào)用方法的參數(shù) 
**/
 
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"我是分離出來(lái)的子線程"];

/*
  第三種創(chuàng)建線程的方式:后臺(tái)線程
  !!!這是NSObject的類別方法,所有只要是NSObject的子類,都可以調(diào)用此方法!
  特點(diǎn):自動(dòng)啟動(dòng)線程,無(wú)法進(jìn)行更詳細(xì)設(shè)置
**/

[self performSelectorInBackground:@selector(run:) withObject:@"我是后臺(tái)線程"];

</br>

2. 線程的狀態(tài)[了解]

//線程的各種狀態(tài):新建-就緒-運(yùn)行-阻塞-死亡

//常用的控制線程狀態(tài)的方法
[NSThread exit];
//退出當(dāng)前線程
[NSThread sleepForTimeInterval:2.0];
//阻塞線程
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
//阻塞線程

//注意:線程死了不能復(fù)生

</br>

3. 線程安全

  • 前提:多個(gè)線程訪問(wèn)同一塊資源會(huì)發(fā)生數(shù)據(jù)安全問(wèn)題
  • 解決方案:加互斥鎖
  • 相關(guān)代碼:@synchronized(self){}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    
    //設(shè)置中票數(shù)
    self.totalCount = 100;
    
     self.threadA = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
     self.threadB = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
     self.threadC = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    
    self.threadA.name = @"售票員A";
    self.threadB.name = @"售票員B";
    self.threadC.name = @"售票員C";
    
    //啟動(dòng)線程
    @synchronized(self) {
        [self.threadA start];
        [self.threadB start];
        [self.threadC start];
    }
    
}

- (void) saleTicket{
    while (1) {
        
    //鎖:必須是全局唯一的
    //1.注意枷鎖的位置
    //2.注意枷鎖的前提條件,多線程共享同一塊資源
    //3.注意加鎖是需要代價(jià)的,需要耗費(fèi)性能的
    //4.加鎖的結(jié)果:線程同步
        
    @synchronized(self) {
        //線程1
        //線程2
        //線程3
        NSInteger count = self.totalCount;
        if (count >0) {
            
            for (NSInteger i = 0; i<1000000; i++) {
            }
            
            self.totalCount = count - 1;
            //賣出去一張票
            NSLog(@"%@賣出去了一張票,還剩下%zd張票", [NSThread currentThread].name,self.totalCount);
        }else
        {
            NSLog(@"不要回公司上班了");
            break;
        }
        }
    } 
}
  • 專業(yè)術(shù)語(yǔ)-線程同步
  • 原子和非原子屬性
  • nonatomic 非原子性 即未在set方法中自動(dòng)添加鎖,可能存在多個(gè)線程對(duì)其修改時(shí)錯(cuò)誤。
  • atomic 原子性 在set方法中自動(dòng)添加鎖
    </br>

4. 線程間通信[即子線程做事,主線程刷新UI]

- (void)touchesBegan:(nonnull NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event{
  //開(kāi)啟一條子線程來(lái)下載圖片 
  [NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];
}

- (void)downloadImage{ 
  //1.確定要下載網(wǎng)絡(luò)圖片的url地址,一個(gè)url唯一對(duì)應(yīng)著網(wǎng)絡(luò)上的一個(gè)資源 
  NSURL *url = [NSURL URLWithString:@"http://p6.qhimg.com/t01d2954e2799c461ab.jpg"]; 

  //2.根據(jù)url地址下載圖片數(shù)據(jù)到本地(二進(jìn)制數(shù)據(jù) )
  NSData *data = [NSData dataWithContentsOfURL:url]; 

  //3.把下載到本地的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成圖片 
  UIImage *image = [UIImage imageWithData:data]; 

  //4.回到主線程刷新UI 
  //4.1 第一種方式
  // [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES]; 
  //4.2 第二種方式
  // [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES]; 
  //4.3 第三種方式
   [self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}

附送:計(jì)算代碼段執(zhí)行時(shí)間


//計(jì)算代碼段執(zhí)行時(shí)間的第一種方法
-(void)download1
{
    //0.000018
    //0.166099
    
    //1.確定URL
    NSURL *url = [NSURL URLWithString:@"http://img4.duitang.com/uploads/blog/201310/18/20131018213446_smUw4.thumb.700_0.jpeg"];

    NSDate *start = [NSDate date];  //獲得當(dāng)前的時(shí)間
    
    //2.根據(jù)url下載圖片二進(jìn)制數(shù)據(jù)到本地
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    
    NSDate *end = [NSDate date];  //獲得當(dāng)前的時(shí)間
    NSLog(@"%f",[end timeIntervalSinceDate:start]);
    
    //3.轉(zhuǎn)換圖片格式
    UIImage *image = [UIImage imageWithData:imageData];
    
    //4.顯示UI
    self.imageView.image = image;
}

//計(jì)算代碼段執(zhí)行時(shí)間的第二種方法
-(void)download2
{
    //1.確定URL
    NSURL *url = [NSURL URLWithString:@"http://img4.duitang.com/uploads/blog/201310/18/20131018213446_smUw4.thumb.700_0.jpeg"];
    
    CFTimeInterval start = CFAbsoluteTimeGetCurrent();
    
    //2.根據(jù)url下載圖片二進(jìn)制數(shù)據(jù)到本地
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    
    CFTimeInterval end = CFAbsoluteTimeGetCurrent();
    NSLog(@"end-start = %f---%f---%f",end - start,end,start);
    
    //3.轉(zhuǎn)換圖片格式
    UIImage *image = [UIImage imageWithData:imageData];
    
    //4.顯示UI
    self.imageView.image = image;
}

</br>

GCD


1. 隊(duì)列和任務(wù)

-(void)asyncConcurrent
{
    //1.1自己創(chuàng)建一個(gè)隊(duì)列
    /*
     第一個(gè)參數(shù):C語(yǔ)言的字符串,標(biāo)簽
     第二個(gè)參數(shù):隊(duì)列的類型
        DISPATCH_QUEUE_CONCURRENT:并發(fā)
        DISPATCH_QUEUE_SERIAL:串行
     */
    //dispatch_queue_t queue = dispatch_queue_create("com.520it.download", DISPATCH_QUEUE_CONCURRENT);
    
    //1.2獲得全局并發(fā)隊(duì)列
    /*
     第一個(gè)參數(shù):優(yōu)先級(jí)
     第二個(gè)參數(shù):
     */
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    NSLog(@"---satrt----");
    
    //2.1>封裝任務(wù)2>添加任務(wù)到隊(duì)列中
    /*
     第一個(gè)參數(shù):隊(duì)列
     第二個(gè)參數(shù):要執(zhí)行的任務(wù)
     */
    dispatch_async(queue, ^{
        NSLog(@"download1----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download2----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download3----%@",[NSThread currentThread]);
    });
    
     NSLog(@"---end----");
}

</br>

2. GCD基本使用[不需要死記,理解即可。注意死鎖的情況]

* 異步函數(shù)+并發(fā)隊(duì)列:開(kāi)啟多條線程,并發(fā)執(zhí)行任務(wù)
* 異步函數(shù)+串行隊(duì)列:開(kāi)啟一條線程,串行執(zhí)行任務(wù)
* 同步函數(shù)+并發(fā)隊(duì)列:不開(kāi)線程,串行執(zhí)行任務(wù)
* 同步函數(shù)+串行隊(duì)列:不開(kāi)線程,串行執(zhí)行任務(wù)
* 異步函數(shù)+主隊(duì)列:不開(kāi)線程,在主線程中串行執(zhí)行任務(wù)
* 同步函數(shù)+主隊(duì)列:不開(kāi)線程,串行執(zhí)行任務(wù)(注意死鎖發(fā)生)

注意同步函數(shù)和異步函數(shù)在執(zhí)行順序上面的差異

//異步函數(shù)+并發(fā)隊(duì)列:會(huì)開(kāi)啟多條線程,隊(duì)列中的任務(wù)是并發(fā)執(zhí)行
-(void)asyncConcurrent
{
    //1.創(chuàng)建隊(duì)列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //2.封裝任務(wù)
    dispatch_async(queue, ^{
        NSLog(@"download1----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download2----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download3----%@",[NSThread currentThread]);
    });
    
     NSLog(@"---end----");
}

//異步函數(shù)+串行隊(duì)列:會(huì)開(kāi)一條線程,隊(duì)列中的任務(wù)是串行執(zhí)行的
-(void)asyncSerial
{
    //1.創(chuàng)建隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_SERIAL);

    //2.封裝操作
    dispatch_async(queue, ^{
        NSLog(@"download1----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download2----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download3----%@",[NSThread currentThread]);
    });
}

//同步函數(shù)+并發(fā)隊(duì)列:不會(huì)開(kāi)線程,任務(wù)是串行執(zhí)行的
-(void)syncConcurrent
{
    //1.創(chuàng)建隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.520it.download", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"---start---");
    //2.封裝任務(wù)
    dispatch_sync(queue, ^{
        NSLog(@"download1----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"download2----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"download3----%@",[NSThread currentThread]);
    });
    
     NSLog(@"---end---");
}

//同步函數(shù)+串行隊(duì)列:不會(huì)開(kāi)線程,任務(wù)是串行執(zhí)行的
-(void)syncSerial
{
    //1.創(chuàng)建隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("com.520it.download", DISPATCH_QUEUE_SERIAL);
    
    //2.封裝任務(wù)
    dispatch_sync(queue, ^{
        NSLog(@"download1----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"download2----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"download3----%@",[NSThread currentThread]);
    });
}

//異步函數(shù)+主隊(duì)列:所有任務(wù)都在主線程中執(zhí)行,不會(huì)開(kāi)線程
-(void)asyncMain
{
    //1.獲得主隊(duì)列
    dispatch_queue_t queue = dispatch_get_main_queue();

    //2.異步函數(shù)
    dispatch_async(queue, ^{
        NSLog(@"download1----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download2----%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"download3----%@",[NSThread currentThread]);
    });
}

//同步函數(shù)+主隊(duì)列:死鎖
//注意:如果該方法在子線程中執(zhí)行,那么所有的任務(wù)在主線程中執(zhí)行,
-(void)syncMain
{
    //1.獲得主隊(duì)列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    NSLog(@"start----");
    //2.同步函數(shù)
    //同步函數(shù):立刻馬上執(zhí)行,如果我沒(méi)有執(zhí)行完畢,那么后面的也別想執(zhí)行
    //異步函數(shù):如果我沒(méi)有執(zhí)行完畢,那么后面的也可以執(zhí)行
    dispatch_sync(queue, ^{
        NSLog(@"download1----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"download2----%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"download3----%@",[NSThread currentThread]);
    });
    
    NSLog(@"end---");
}

</br>

3. GCD線程間通信[子線程操作,主線程刷新UI]

  //0.獲取一個(gè)全局的隊(duì)列 
  dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 

  //1.先開(kāi)啟一個(gè)線程,把下載圖片的操作放在子線程中處理 
  dispatch_async(queue, ^{ 

  //2.下載圖片 
  NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c3320b14bb3184c510fd8f9a185.jpg"]; 
  NSData *data = [NSData dataWithContentsOfURL:url]; 
  UIImage *image = [UIImage imageWithData:data]; 
  NSLog(@"下載操作所在的線程--%@",[NSThread currentThread]);

    //3.回到主線程刷新UI 
    dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; 
    //打印查看當(dāng)前線程 
    NSLog(@"刷新UI---%@",[NSThread currentThread]); 
    });
  });

</br>

4. GCD其他常用函數(shù)

  • 柵欄函數(shù)(控制任務(wù)的執(zhí)行順序)
    //0.獲得全局并發(fā)隊(duì)列
    //柵欄函數(shù)不能使用全局并發(fā)隊(duì)列
    //dispatch_queue_t queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
    
    //1.異步函數(shù)
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<100; i++) {
            NSLog(@"download1-%zd-%@",i,[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{  
        for (NSInteger i = 0; i<100; i++) {
            NSLog(@"download2-%zd-%@",i,[NSThread currentThread]);
        }
    });
     
    //柵欄函數(shù)
    dispatch_barrier_async(queue, ^{
        NSLog(@"+++++++++++++++++++++++++++++");
    });
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<100; i++) {
            NSLog(@"download3-%zd-%@",i,[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<100; i++) {
            NSLog(@"download4-%zd-%@",i,[NSThread currentThread]);
        }
    });

執(zhí)行結(jié)果是先打印download1和download2 [前后順序不一定],然后打印+++,最后再打印download3和download4 [前后順序不一定]
</br>

  • 延遲執(zhí)行[一定時(shí)間后,將執(zhí)行的操作加入到隊(duì)列中]
//1.第一種用法    
/* 
   NSEC_PER_SEC 秒     
   NSEC_PER_MSEC 毫秒    
   NSEC_PER_USEC 微秒     
*/    

/*
   DISPATCH_TIME_NOW  從現(xiàn)在開(kāi)始
   DISPATCH_TIME_FOREVER 從此刻到永遠(yuǎn)
*/

//5秒之后
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC));       

//5秒后在主線程執(zhí)行
dispatch_after(time, dispatch_get_main_queue(), ^{        
//執(zhí)行操作       
 NSLog(@"after 5s");    
});

!!!!!需要注意的是,使用dispatch_after實(shí)現(xiàn)延遲執(zhí)行某動(dòng)作,時(shí)間并不是很精確,實(shí)際上是過(guò)多久將Block追加到main Queue中,而不是執(zhí)行該動(dòng)作,理論上是:主線程 RunLoop 1/60秒檢測(cè)時(shí)間,追加的時(shí)間范圍 5s~(5+1/60)s
如果此時(shí)main queue中的任務(wù)很多,沒(méi)有執(zhí)行完畢,那么新添加的這個(gè)動(dòng)作就要繼續(xù)推遲。如果對(duì)時(shí)間的精確度沒(méi)有高要求,只是為了推遲執(zhí)行,那么使用dispatch_after還是很不錯(cuò)的。

//2.第二種用法  
//執(zhí)行的c語(yǔ)言方法    
dispatch_after_f(dispatch_time(DISPATCH_TIME_NOW, 3ull *NSEC_PER_SEC), dispatch_get_main_queue(), NULL, fun1);

//c函數(shù)
void fun1(){   
 NSLog(@"after 3s");
}
//3.第三種用法    
//其實(shí)和第一種方法是一樣的,只是參數(shù)已經(jīng)幫我們填寫好了,我們只需要填寫時(shí)間和執(zhí)行block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 
        NSLog(@"after 5s");   
 });

</br>

  • 只執(zhí)行一次的代碼[單例模式]
static XMGTool *_instance;

//本身就是線程安全的
static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{
    _instance = [super allocWithZone:zone];
});
    
return _instance;

</br>

  • 快速迭代(開(kāi)多個(gè)線程并發(fā)完成迭代操作)
//一般的遍歷操作,我們放在一個(gè)for循環(huán)中執(zhí)行
- (void)forDemo{
    //同步
    for (NSInteger i = 0; i<10; i++) {
        NSLog(@"%zd---%@",i,[NSThread currentThread]);
    }
}

//但是如果遍歷的操作需要更快的執(zhí)行完,我們就可以使用dispatch_apply
//開(kāi)子線程和主線程一起完成遍歷任務(wù),任務(wù)的執(zhí)行時(shí)并發(fā)的。
-(void)applyDemo{
     /*
     第一個(gè)參數(shù):遍歷的次數(shù)
     第二個(gè)參數(shù):隊(duì)列(并發(fā)隊(duì)列)
     第三個(gè)參數(shù):index 索引
     */
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"%zd---%@",index,[NSThread currentThread]);
    });
}

接下來(lái)我們拿一個(gè)移動(dòng)文件的例子來(lái)說(shuō)明遍歷的作用:

//使用for循環(huán)
-(void)moveFile
{
    //1.拿到文件路徑
    NSString *from = @"/Users/xiaomage/Desktop/from";
    
    //2.獲得目標(biāo)文件路徑
    NSString *to = @"/Users/xiaomage/Desktop/to";
    
    //3.得到目錄下面的所有文件
    NSArray *subPaths = [[NSFileManager defaultManager] subpathsAtPath:from];
    
    //4.遍歷所有文件,然后執(zhí)行剪切操作
    NSInteger count = subPaths.count;
    
    for (NSInteger i = 0; i< count; i++) {
        
        //4.1 拼接文件的全路徑
       // NSString *fullPath = [from stringByAppendingString:subPaths[i]];
        NSString *fullPath = [from stringByAppendingPathComponent:subPaths[i]];
        NSString *toFullPath = [to stringByAppendingPathComponent:subPaths[i]];
        
        NSLog(@"%@",fullPath);
        //4.2 執(zhí)行剪切操作
        /*
         第一個(gè)參數(shù):要剪切的文件在哪里
         第二個(gè)參數(shù):文件應(yīng)該被存到哪個(gè)位置
         */
        [[NSFileManager defaultManager]moveItemAtPath:fullPath toPath:toFullPath error:nil];
        
        NSLog(@"%@---%@--%@",fullPath,toFullPath,[NSThread currentThread]);
    }
}

-(void)moveFileWithGCD
{
    //1.拿到文件路徑
    NSString *from = @"/Users/xiaomage/Desktop/from";
    
    //2.獲得目標(biāo)文件路徑
    NSString *to = @"/Users/xiaomage/Desktop/to";
    
    //3.得到目錄下面的所有文件
    NSArray *subPaths = [[NSFileManager defaultManager] subpathsAtPath:from];
    
    NSLog(@"%@",subPaths);
    //4.遍歷所有文件,然后執(zhí)行剪切操作
    NSInteger count = subPaths.count;
    
    dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
        //4.1 拼接文件的全路徑
        // NSString *fullPath = [from stringByAppendingString:subPaths[i]];
        //在拼接的時(shí)候會(huì)自動(dòng)添加/
        NSString *fullPath = [from stringByAppendingPathComponent:subPaths[i]];
        NSString *toFullPath = [to stringByAppendingPathComponent:subPaths[i]];
        
        NSLog(@"%@",fullPath);
        //4.2 執(zhí)行剪切操作
        /*
         第一個(gè)參數(shù):要剪切的文件在哪里
         第二個(gè)參數(shù):文件應(yīng)該被存到哪個(gè)位置
         */
        [[NSFileManager defaultManager]moveItemAtPath:fullPath toPath:toFullPath error:nil];
     
        NSLog(@"%@---%@--%@",fullPath,toFullPath,[NSThread currentThread]);
    });
}

需要注意的是,并不是count為多少就開(kāi)啟多少個(gè)線程,系統(tǒng)會(huì)自己判斷需要開(kāi)啟多少個(gè)線程來(lái)完成此次遍歷。換句話說(shuō),如果遍歷時(shí)已經(jīng)開(kāi)啟了兩條異步線程,這時(shí)第三條數(shù)據(jù)需要異步操作,系統(tǒng)會(huì)判斷之前的線程有沒(méi)有空閑的,如果有空閑的線程,那么不會(huì)創(chuàng)建新的線程,而是用空閑線程來(lái)完成任務(wù)。

</br>

  • 隊(duì)列組

如果某個(gè)界面上有兩個(gè)接口請(qǐng)求,我們需要在兩個(gè)接口都返回的情況下刷新UI,那么我們可以使用隊(duì)列組來(lái)實(shí)現(xiàn)。


//1.創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
//2.創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
    
//3.異步函數(shù)
 /*
     1)封裝任務(wù)
     2)把任務(wù)添加到隊(duì)列中
     3)dispatch_group_notify會(huì)監(jiān)聽(tīng)任務(wù)的執(zhí)行情況,通知group
*/
    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]);
    });
    
    //攔截通知,當(dāng)隊(duì)列組中所有的任務(wù)都執(zhí)行完畢的時(shí)候回進(jìn)入到下面的方法
    dispatch_group_notify(group, queue, ^{        
        NSLog(@"-------dispatch_group_notify-------");
    });

之前我們創(chuàng)建一個(gè)異步線程操作是使用方法dispatch_async,而這里是使用dispatch_group_async

第二種實(shí)現(xiàn)方法:

//1.創(chuàng)建隊(duì)列
dispatch_queue_t queue =dispatch_get_global_queue(0, 0);
    
//2.創(chuàng)建隊(duì)列組
dispatch_group_t group = dispatch_group_create();
    
//3.在該方法后面的異步任務(wù)會(huì)被納入到隊(duì)列組的監(jiān)聽(tīng)范圍,進(jìn)入群組
//dispatch_group_enter|dispatch_group_leave 必須要配對(duì)使用
    
dispatch_group_enter(group);
    
dispatch_async(queue, ^{
  NSLog(@"1----%@",[NSThread currentThread]);
  
  //離開(kāi)群組
  dispatch_group_leave(group);
});
    
dispatch_group_enter(group);
    
dispatch_async(queue, ^{
  NSLog(@"2----%@",[NSThread currentThread]);
    
  //離開(kāi)群組
  dispatch_group_leave(group);
});
    
    
//攔截通知
//問(wèn)題?該方法是阻塞的嗎?  內(nèi)部本身是異步的,所以不會(huì)
//    dispatch_group_notify(group, queue, ^{
//        NSLog(@"-------dispatch_group_notify-------");
//    });
    
//等待.死等. 直到隊(duì)列組中所有的任務(wù)都執(zhí)行完畢之后才能執(zhí)行
//阻塞的
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"----end----");

</br>

NSOperation


  • 相關(guān)概念

    • NSOperation是對(duì)GCD的包裝
    • 兩個(gè)核心概念[隊(duì)列][操作]
  • 基本使用

    • NSOperation本身是抽象類,我們使用的是他的子類
    • 三個(gè)子類分別是:NSBlockOperation, NSInvocationOperation以及自定義繼承自NSOperation的類
    • NSOperation單獨(dú)使用無(wú)法實(shí)現(xiàn)多線程,需要和NSOperationQueue結(jié)合使用才能實(shí)現(xiàn)。
  • 相關(guān)代碼

//  01 NSInvocationOperation
    //1.封裝操作
    /*
     第一個(gè)參數(shù):目標(biāo)對(duì)象
     第二個(gè)參數(shù):該操作要調(diào)用的方法,最多接受一個(gè)參數(shù)
     第三個(gè)參數(shù):調(diào)用方法傳遞的參數(shù),如果方法不接受參數(shù),那么該值傳nil
     */
    //必須添加隊(duì)列才會(huì)生成新線程

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

    //2.啟動(dòng)操作
    [operation start];
-------------------------------------------------
    //  02 NSBlockOperation
    //1.封裝操作
    /*
     NSBlockOperation提供了一個(gè)類方法,在該類方法中封裝操作
     */
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        //在主線程中執(zhí)行
        NSLog(@"---download1--%@",[NSThread currentThread]);
    }];

    //2.追加操作,追加的操作在子線程中執(zhí)行

    //追加任務(wù)
    //注意:如果一個(gè)操作中的任務(wù)數(shù)量大于1,那么會(huì)開(kāi)子線程并發(fā)執(zhí)行任務(wù)
    //注意:不一定是子線程,有可能是主線程
    [operation addExecutionBlock:^{
        NSLog(@"---download2--%@",[NSThread currentThread]);
    }];

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

    //3.啟動(dòng)執(zhí)行操作
    [operation start];

----------------------------------------------
// 03 自定義NSOperation
    //如何封裝操作?
    //自定義的NSOperation,通過(guò)重寫內(nèi)部的main方法實(shí)現(xiàn)封裝操作
    -(void)main
    {
        NSLog(@"--main--%@",[NSThread currentThread]);
    }

    //如何使用?
    //1.實(shí)例化一個(gè)自定義操作對(duì)象
    XMGOperation *op = [[XMGOperation alloc]init];

    //2.執(zhí)行操作
    [op start];

</br>

  • NSOperationQueue基本使用

    • NSOperationQueue中的兩種隊(duì)列
      • 主隊(duì)列 通過(guò)mainQueue獲得,凡是放到主隊(duì)列中的任務(wù)都是將在主線程執(zhí)行
      • 非主隊(duì)列 直接alloc init出來(lái)的隊(duì)列。非主隊(duì)列同時(shí)具備了并發(fā)和串行的功能,通過(guò)設(shè)置最大并發(fā)數(shù)屬性來(lái)控制任務(wù)是并發(fā)任務(wù)還是串行任務(wù)。
  • 相關(guān)代碼

//自定義NSOperation
-(void)customOperation
{
    //1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    //2.封裝操作
    //好處:1.信息隱蔽
    //2.代碼復(fù)用

    XMGOperation *op1 = [[XMGOperation alloc]init];
    XMGOperation *op2 = [[XMGOperation alloc]init];

    //3.添加操作到隊(duì)列中
    [queue addOperation:op1];
    [queue addOperation:op2];
}

//NSBlockOperation
- (void)block
{
    //1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    //2.封裝操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1----%@",[NSThread currentThread]);
    }];

    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2----%@",[NSThread currentThread]);

    }];

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

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

    //3.添加操作到隊(duì)列中
    [queue addOperation:op1];
    [queue addOperation:op2];

    //補(bǔ)充:簡(jiǎn)便方法
    [queue addOperationWithBlock:^{
        NSLog(@"5----%@",[NSThread currentThread]);
    }];

}

//NSInvocationOperation
- (void)invocation
{
    /*
     GCD中的隊(duì)列:
     串行隊(duì)列:自己創(chuàng)建的,主隊(duì)列
     并發(fā)隊(duì)列:自己創(chuàng)建的,全局并發(fā)隊(duì)列

     NSOperationQueue
     主隊(duì)列:[NSOperationQueue mainqueue];凡事放在主隊(duì)列中的操作都在主線程中執(zhí)行
     非主隊(duì)列:[[NSOperationQueue alloc]init],并發(fā)和串行,默認(rèn)是并發(fā)執(zhí)行的
     */

    //1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    //2.封裝操作
    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];

    //3.把封裝好的操作添加到隊(duì)列中
    //不需要手動(dòng)調(diào)用[op1 start]
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
}

</br>

  • NSOperation其他用法
    • 設(shè)置最大并發(fā)數(shù)[控制任務(wù)并發(fā)和串行]
//1.創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];

//2.設(shè)置最大并發(fā)數(shù)量 maxConcurrentOperationCount

//同一時(shí)間最多有多少個(gè)任務(wù)可以執(zhí)行(實(shí)際開(kāi)起的線程總數(shù)大于maxConcurrentOperationCount)
//串行執(zhí)行任務(wù)!=只開(kāi)一條線程 (線程同步)
// maxConcurrentOperationCount >1 那么就是并發(fā)隊(duì)列
// maxConcurrentOperationCount == 1 那就是串行隊(duì)列
// maxConcurrentOperationCount == 0  不會(huì)執(zhí)行任務(wù)
// maxConcurrentOperationCount == -1 特殊意義 最大值 表示不受限制
queue.maxConcurrentOperationCount = 2;
  • 暫停和恢復(fù)以及取消
//設(shè)置暫停和恢復(fù)
//suspended設(shè)置為YES表示暫停,suspended設(shè)置為NO表示恢復(fù)
//暫停表示不繼續(xù)執(zhí)行隊(duì)列中的下一個(gè)任務(wù),暫停操作是可以恢復(fù)的
if (self.queue.isSuspended) {
      self.queue.suspended = NO;
}else
{
      self.queue.suspended = YES;
}

//取消隊(duì)列里面的所有操作
//取消之后,當(dāng)前正在執(zhí)行的操作的下一個(gè)操作將不再執(zhí)行,而且永遠(yuǎn)都不在執(zhí)行,就像后面的所有任務(wù)都從隊(duì)列里面移除了一樣
//取消操作是不可以恢復(fù)的
[self.queue cancelAllOperations];

---------自定義NSOperation取消操作--------------------------
//自定義NSOperation子類需要重寫main方法,用于執(zhí)行線程中的操作
-(void)main
{
    //耗時(shí)操作1
    for (int i = 0; i<1000; i++) {
        NSLog(@"任務(wù)1-%d--%@",i,[NSThread currentThread]);
    }
    NSLog(@"+++++++++++++++++++++++++++++++++");

    //蘋果官方建議,每當(dāng)執(zhí)行完一次耗時(shí)操作之后,就查看一下當(dāng)前隊(duì)列是否為取消狀態(tài),如果是,那么就直接退出
    //好處是可以提高程序的性能
    if (self.isCancelled) {
        return;
    }

    //耗時(shí)操作2
    for (int i = 0; i<1000; i++) {
        NSLog(@"任務(wù)1-%d--%@",i,[NSThread currentThread]);
    }

    NSLog(@"+++++++++++++++++++++++++++++++++");
}
  • 下載多張圖片合成綜合案例(設(shè)置操作依賴)
/*
 1.下載圖片1
 2.下載圖片2
 3.合并圖片
 */
-(void)comBie
{
    //1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    __block UIImage *image1;
    __block UIImage *image2;
    //2 封裝操作,下載圖片1
    NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSURL *url = [NSURL URLWithString:@"http://s15.sinaimg.cn/bmiddle/4c0b78455061c1b7f1d0e"];
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        image1 = [UIImage imageWithData:imageData];
        
        NSLog(@"download---%@",[NSThread currentThread]);
        
    }];
    
    //3 封裝操作,下載圖片2
    NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSURL *url = [NSURL URLWithString:@"http://www.027art.com/feizhuliu/UploadFiles_6650/201109/2011091718442835.jpg"];
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        image2 = [UIImage imageWithData:imageData];
        
        NSLog(@"download---%@",[NSThread currentThread]);
        
    }];
    
    //4.封裝合并圖片的操作
    NSBlockOperation *combie = [NSBlockOperation blockOperationWithBlock:^{
        //4.1 開(kāi)上下文
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        
        //4.2 畫圖1
        [image1 drawInRect:CGRectMake(0, 0, 100, 200)];
        
        //4.3 畫圖2
        [image2 drawInRect:CGRectMake(100, 0, 100, 200)];
        
        //4.4 根據(jù)上下文得到圖片
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        
        //4.5 關(guān)閉上下文
        UIGraphicsEndImageContext();
        
        //7.更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageView.image = image;
            NSLog(@"UI----%@",[NSThread currentThread]);
        }];
        
    }];
    
    //5.設(shè)置依賴關(guān)系
    //依賴的作用是:線程會(huì)在依賴執(zhí)行完成后,再執(zhí)行
    [combie addDependency:download1];
    [combie addDependency:download2];
    
    //6.添加操作到隊(duì)列中
    [queue addOperation:download2];
    [queue addOperation:download1];
    [queue addOperation:combie];
}

填坑...

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

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

  • Object C中創(chuàng)建線程的方法是什么?如果在主線程中執(zhí)行代碼,方法是什么?如果想延時(shí)執(zhí)行代碼、方法又是什么? 1...
    AlanGe閱讀 1,916評(píng)論 0 17
  • 原文:http://www.cocoachina.com/ios/20170707/19769.html 本文主要...
    冬的天閱讀 2,417評(píng)論 0 12
  • 多線程 在iOS開(kāi)發(fā)中為提高程序的運(yùn)行效率會(huì)將比較耗時(shí)的操作放在子線程中執(zhí)行,iOS系統(tǒng)進(jìn)程默認(rèn)啟動(dòng)一個(gè)主線程,用...
    郭豪豪閱讀 2,720評(píng)論 0 4
  • 什么是進(jìn)程? 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序。 每個(gè)進(jìn)程之間是獨(dú)立的,每個(gè)進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存...
    珍此良辰閱讀 1,412評(píng)論 1 5
  • 從哪說(shuō)起呢? 單純講多線程編程真的不知道從哪下嘴。。 不如我直接引用一個(gè)最簡(jiǎn)單的問(wèn)題,以這個(gè)作為切入點(diǎn)好了 在ma...
    Mr_Baymax閱讀 2,910評(píng)論 1 17

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