iOS篇-線程篇-0基礎(chǔ)到熟練應(yīng)用多線程( 二 )

//:如有需要代碼demo 在評(píng)論留下郵箱 即刻回復(fù)

一 : 科普一分鐘

上一期簡(jiǎn)單普及了一下有多線程的知識(shí).
如何創(chuàng)建子線程,對(duì)于線程的控制,如何對(duì)UI 線程的操作.
在實(shí)際開發(fā)應(yīng)用中,我們用到子線程,進(jìn)行耗時(shí)任務(wù)操作,并發(fā)任務(wù)之間是如何依賴的,這一期將詳細(xì)講解.

我們?cè)谫I火車票的時(shí)候?yàn)槭裁床粫?huì)購(gòu)買重復(fù)嗎,對(duì)于線程安全也會(huì)有詳細(xì)講解.

二 : 詳細(xì)講解iOS中多線程方案

1. pthread
  • 簡(jiǎn)單了解即可,在開發(fā)應(yīng)用中非常少. 首先導(dǎo)入庫(kù) #import <pthread.h>
  • 線程對(duì)象的創(chuàng)建

 //1.pthread 創(chuàng)建線程對(duì)象
    pthread_t thread;
    
     pthread_t thread1 = NULL;
  • 創(chuàng)建線程
 /**


     
     第一個(gè)參數(shù):線程對(duì)象傳遞地址
     第二個(gè)參數(shù):線程的屬性 NULL
     第三個(gè)參數(shù):指向函數(shù)的指針
     第四個(gè)參數(shù):函數(shù)需要接受的參數(shù)
     
     */
    
    pthread_create(&thread, NULL,TZtest, NULL);
    
void *TZtest (void *parm){
    
  NSLog(@"--%@",[NSThread mainThread]);
    return NULL;
    
}

  • 判斷兩個(gè)條線程是否相等
pthread_equal(thread, thread1);
  • 應(yīng)用

記得之前在 viewDidLoad 寫過一個(gè)耗時(shí)操作

//耗時(shí)操作
    for (int i = 0; i < 100000; i++) {
        NSLog(@" i =  %d ---currentThread = %@",i,[NSThread mainThread]);
        
    }
    


現(xiàn)在我們把這個(gè)方法放入我們的創(chuàng)建的線程方法里

- (void)viewDidLoad {
    [super viewDidLoad];

   pthread_t thread;
   pthread_create(&thread, NULL,test, NULL);

}
void *test (void *parm){
    
    for (int i = 0; i < 100000; i++) {
        NSLog(@" i =  %d ---currentThread = %@",i,[NSThread mainThread]);
        
    }
    
  NSLog(@"--%@",[NSThread mainThread]);
    return NULL;
    
}
2. NSThread
  • 創(chuàng)建線程的的方式
    1.第一種方式 手動(dòng)啟動(dòng)線程
     參數(shù)1 : 目標(biāo)對(duì)象
     參數(shù)2 : 方法選擇器
     參數(shù)3 : 前面調(diào)用方法需要傳遞的參數(shù) 沒有時(shí)傳nil
 NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(test:) object:@"TZ"];

 //啟動(dòng)線程
    [thread start];

-(void)test:(NSString *)str{
    
    NSLog(@"--%@",[NSThread mainThread]);
    

}

2.第二種方式 類方法 自動(dòng)啟動(dòng)

//創(chuàng)建線程的第二種方法 自動(dòng)啟動(dòng)線程
    [NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:@"TZ"];

3.第三種方法

 //開啟一條后臺(tái)線程
    [self performSelectorInBackground:@selector(test:) withObject:@"TZ"];
  • 屬性 屬性在啟動(dòng)線程之前調(diào)用
    name

 NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(test:) object:@"TZ"];
    
    //設(shè)置屬性在啟動(dòng)線程之前設(shè)置
    thread.name = @"A";

threadPriority

 TZThread *thread = [[TZThread alloc]initWithTarget:self selector:@selector(test:) object:@"TZ"];
    
    //設(shè)置屬性在啟動(dòng)線程之前設(shè)置
   //線程的優(yōu)先級(jí)  取值范圍 0.0 - 1.0之間  默認(rèn)優(yōu)先級(jí) 0.5
    thread.threadPriority = 1.0 ;
  • 線程的周期
    生命周期,當(dāng)任務(wù)執(zhí)行完畢后被釋放掉

  • 線程的狀態(tài)
    當(dāng)線程創(chuàng)建好后 執(zhí)行start 方法后 --->線程進(jìn)入 就緒狀態(tài)-runable--> 當(dāng)CPU 調(diào)度線程的時(shí)候 線程進(jìn)入--->運(yùn)行狀態(tài)-Running<------當(dāng)CPU 調(diào)度其他線程時(shí)候 狀態(tài)變?yōu)?就緒狀態(tài)-runable----->當(dāng)調(diào)用sleep方法等待同步鎖---->線程進(jìn)入阻塞狀態(tài)------>當(dāng)線程任務(wù)執(zhí)行完畢/異常退出/強(qiáng)制退出(exit)---->線程進(jìn)入死亡狀態(tài)-Dead

  • !注意:

當(dāng)線程執(zhí)行start后 線程對(duì)象放入 可調(diào)度線程池
當(dāng)線程進(jìn)入阻塞狀態(tài)時(shí)候 線程對(duì)象還在內(nèi)存中 只不過暫時(shí)從可調(diào)度線程池移除
當(dāng)線程進(jìn)入死亡狀態(tài)時(shí) 線程對(duì)象內(nèi)存中移除

從可調(diào)度線程池移除.png
進(jìn)入可調(diào)度線程池.png
  • 阻塞方法
   //阻塞線程
    [NSThread sleepForTimeInterval:2];
//3秒后
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]];
  • 強(qiáng)制退出方法
  [NSThread exit];//退出當(dāng)前線程 強(qiáng)制退出
  • 線程之間的通信
    1.我們?nèi)ッ嬖嚮蛘呓涣鞯臅r(shí)候總會(huì)被人們問到一個(gè)問題,一個(gè)線程讀取完數(shù)據(jù)怎么再次操作另一個(gè)線程呢...等等
    其實(shí)這就是線程之間的通信

    2.線程通信體現(xiàn)在

1.一個(gè)線程傳遞數(shù)據(jù)給另一個(gè)線程
2.在一個(gè)線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另一個(gè)線程繼續(xù)執(zhí)行任務(wù).

3.線程通信的常用方法

回到主線程,第三個(gè)參數(shù)的意思 就是 是否等待執(zhí)行,如果選擇`YES` 則知道回到主線程 參數(shù)的方法執(zhí)行完成后 繼續(xù)執(zhí)行,

否則的話 直接執(zhí)行 該方法下面的函數(shù)

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

回到某個(gè)線程

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);

4.代碼實(shí)現(xiàn)
我們寫一個(gè)下載圖片的代碼 完成線程間通信

 - (void)viewDidLoad {
 [super viewDidLoad];
  [NSThread detachNewThreadSelector:@selector(downLoad) toTarget:self withObject:nil];
 
}

-(void)downLoad{
// http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg

    

    NSURL *URL  = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
    
    //根據(jù)URL 下載圖片  二進(jìn)制數(shù)據(jù) 到本地
    
    NSData *imageData = [NSData dataWithContentsOfURL:URL];
    
    //3.轉(zhuǎn)換圖片格式
    UIImage *image = [UIImage imageWithData:imageData];
    
    //回到主線程顯示UI
    
//    [self performSelectorOnMainThread:@selector(TZshowUI:) withObject:image waitUntilDone:YES];
    
//    [self performSelector:@selector(TZshowUI:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
    
    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
    
     
}
-(void)TZshowUI:(UIImage*)image{
self.imageView.image = image;

}
3. GCD
  1. 概括 : Grand Central DisPatch 中樞調(diào)度器
    純C語言,提供了非常多強(qiáng)大的函數(shù)

  2. GCD 的優(yōu)勢(shì) : GCD 是蘋果為多核的并行運(yùn)算提出的解決方案
    GCD會(huì)自動(dòng)利用更多的CPU 內(nèi)核
    GCD會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程,調(diào)度任務(wù),銷毀線程)
    程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫任何線程管理的代碼

  3. CCD 最大的兩個(gè)特性任務(wù)隊(duì)列
    任務(wù): 執(zhí)行什么操作
    隊(duì)列:用來存放任務(wù)
    GCD使用的兩個(gè)步驟
    a : 定制任務(wù)
    b : 將任務(wù)添加到隊(duì)列中

    GCD 會(huì)自動(dòng)將隊(duì)列中的任務(wù)取出,放到對(duì)應(yīng)的棧中執(zhí)行
    任務(wù)的取出時(shí)遵循隊(duì)列的FIFO 原則,先進(jìn)先出,后進(jìn)后

  4. 常用函數(shù)
    a : 同步函數(shù) 參數(shù) : 1.隊(duì)列 2 block 想做的事(任務(wù))

     dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
    

    b : 異步函數(shù) 參數(shù) : 1.隊(duì)列 2 block 想做的事(任務(wù))

     dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    
  5. 同步函數(shù)與異步函數(shù)的區(qū)別

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

  6. 隊(duì)列的類型

    并發(fā)隊(duì)列 : 可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行 ,自動(dòng)開啟多個(gè)線程同時(shí)執(zhí)行任務(wù),并發(fā)功能只有在異步函數(shù)dispatch_asyn下才有效

    串行隊(duì)列 : 讓任務(wù)一個(gè)接一個(gè)地執(zhí)行,一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)

  7. 基礎(chǔ)代碼

a : 創(chuàng)建異步函數(shù)+并發(fā)隊(duì)列 : 會(huì)開啟多條線程,隊(duì)列中的任務(wù)是異步執(zhí)行

-(void)TZasyncConCurrent{
    
//1.創(chuàng)建隊(duì)列
    
    /**
     參數(shù)1 : C語言的字符串,標(biāo)簽
     參數(shù)2 : 隊(duì)列的類型
     DISPATCH_QUEUE_CONCURRENT 并發(fā)
     DISPATCH_QUEUE_SERIAL 串行
     
     */
//    dispatch_queue_t queue = dispatch_queue_create("TZzzz", DISPATCH_QUEUE_CONCURRENT);
    
    
    
    //獲取全局并發(fā)隊(duì)列 從系統(tǒng)中獲得
    /**
     參數(shù)一 : 優(yōu)先級(jí)
     參數(shù)二 : 未來使用
     */
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
   //2 1->封裝任務(wù) 2->添加任務(wù)到隊(duì)列中
    
    /**
     
     參數(shù)1 : 隊(duì)列
     參數(shù)2 : 要執(zhí)行的任務(wù)
     
     */

    
    dispatch_async(queue, ^{
        
        NSLog(@"-TZ1--%@",[NSThread mainThread]);
        
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"-TZ2--%@",[NSThread mainThread]);
        
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"-TZ3--%@",[NSThread mainThread]);
        
    });
    
}

b : 創(chuàng)建異步函數(shù)+串行隊(duì)列 : 會(huì)開線程,開一條線程,隊(duì)列中的任務(wù)是串行執(zhí)行的

//異步函數(shù)+串行隊(duì)列 : 會(huì)開線程,開一條線程,隊(duì)列中的任務(wù)是串行執(zhí)行的
-(void)TZasyncSerial{

    //1.創(chuàng)建隊(duì)列
  dispatch_queue_t queue = dispatch_queue_create("TZzzzz", DISPATCH_QUEUE_SERIAL);
    

    //2.封裝操作
    dispatch_async(queue, ^{
        
        NSLog(@"-TZ1--%@",[NSThread mainThread]);
        
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"-TZ2--%@",[NSThread mainThread]);
        
    });
    dispatch_async(queue, ^{
        
        NSLog(@"-TZ3--%@",[NSThread mainThread]);
        
    });
}

c : 創(chuàng)建同步函數(shù)+并發(fā)隊(duì)列 : 不會(huì)開線程,任務(wù)是串行執(zhí)行的

-(void)TZsyncConCurrent{

    //創(chuàng)建并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("TZzzz", DISPATCH_QUEUE_CONCURRENT);
    
    //封裝任務(wù)
    //2.封裝操作
    dispatch_sync(queue, ^{
        
        NSLog(@"-TZ1--%@",[NSThread mainThread]);
        
    });
    
    dispatch_sync(queue, ^{
        
        NSLog(@"-TZ2--%@",[NSThread mainThread]);
        
    });
    dispatch_sync(queue, ^{
        
        NSLog(@"-TZ3--%@",[NSThread mainThread]);
        
    });
}

d : 創(chuàng)建同步函數(shù)+串行隊(duì)列 : 不會(huì)開線程,任務(wù)是串行執(zhí)行的

-(void)TZsyncSerial{
    
    //創(chuàng)建串行隊(duì)列
    dispatch_queue_t queue = dispatch_queue_create("TZzzz", DISPATCH_QUEUE_SERIAL);
    
    //封裝任務(wù)
    //2.封裝操作
    dispatch_sync(queue, ^{
        
        NSLog(@"-TZ1--%@",[NSThread mainThread]);
        
    });
    
    dispatch_sync(queue, ^{
        
        NSLog(@"-TZ2--%@",[NSThread mainThread]);
        
    });
    dispatch_sync(queue, ^{
        
        NSLog(@"-TZ3--%@",[NSThread mainThread]);
        
    });
}

8.GCD 線程間的通信
我們還是以下載圖片為例子

 //創(chuàng)建子線程 下載圖片
   
   
   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   
   dispatch_async(queue, ^{
       
       //1.1
        NSURL *URL  = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
       //1.2 下載二進(jìn)制數(shù)據(jù)到本地
       NSData *imageData = [NSData dataWithContentsOfURL:URL];
       UIImage *image = [UIImage imageWithData:imageData];
       
       //更新UI
       dispatch_async(dispatch_get_main_queue(), ^{
           self.imageView.image = image;

       });
       
       
   });

我們開啟了子線程,然后進(jìn)行下載 .回到主線程做事情(刷新UI)

9.GCD 常用函數(shù)
a : 延遲
我們平時(shí)為了在某個(gè)地方停留一會(huì)用的比較多的就是延遲函數(shù)

```
//1. 延遲執(zhí)行的第一種方法
NSLog(@"--start");
[self performSelector:@selector(tztask) withObject:nil afterDelay:2];


//2.延遲執(zhí)行的第二種方法

[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(tztask) userInfo:nil repeats:NO];

//3.延遲執(zhí)行的第三種方法
/**
 參數(shù)1 : DISPATCH_TIME_NOW 從現(xiàn)在開始計(jì)算時(shí)間
 參數(shù)2 : 延遲的時(shí)間 2.0 GCD 時(shí)間單位為納秒
 參數(shù)3 : 隊(duì)列
 */

//傳入全局并發(fā)隊(duì)列打印操作在子線程執(zhí)行
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue = dispatch_get_main_queue;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), (queue), ^{
    
    NSLog(@"--GCD----%@",[NSThread currentThread]);
    
});
```

延遲不僅可以在主線程(UI)線程中進(jìn)行,也可以在子線程進(jìn)行,當(dāng)隊(duì)列為并發(fā)隊(duì)列時(shí)候再子線程執(zhí)行 ,主隊(duì)列時(shí)候再主線程執(zhí)行. 使用時(shí)請(qǐng)大家注意

b : 一次性代碼-大多數(shù)情況用在單利中

//整個(gè)應(yīng)用程序只調(diào)用一次
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       NSLog(@"===onece");
       
   });

注意:一次性代碼不用放在懶加載里面,因?yàn)橐淮涡源a 整個(gè)app 啟動(dòng)后只會(huì)運(yùn)行一次.

10.GCD 柵欄函數(shù)
他是干什么的呢, 在多個(gè)異步任務(wù),我們?cè)趺匆?guī)范順序呢 這就是柵欄函數(shù)的作用,像圍欄一樣控制你想要的順序

 //柵欄函數(shù)不能使用全局并發(fā)隊(duì)列
  dispatch_queue_t queue = dispatch_queue_create("TZdown", DISPATCH_QUEUE_CONCURRENT);
  
  
  
  dispatch_async(queue, ^{
      
      NSLog(@"-TZ1--%@",[NSThread mainThread]);
      
  });
  
  dispatch_async(queue, ^{
      
      NSLog(@"-TZ2--%@",[NSThread mainThread]);
      
  });
  
  //柵欄函數(shù)
  
  dispatch_barrier_async(queue, ^{
      
      NSLog(@"+++++++++圍欄++++++++++");
      
  });
  dispatch_async(queue, ^{
      
      NSLog(@"-TZ3--%@",[NSThread mainThread]);
      
  });

運(yùn)行結(jié)果 : TZ1 TZ2 打印順序隨意,因?yàn)槭钱惒降?,柵欄在其后,最后執(zhí)行的是TZ3任務(wù)

柵欄函數(shù)注意點(diǎn):控制并發(fā)隊(duì)列任務(wù)執(zhí)行順序,不能使用全局并發(fā)隊(duì)列

11.GCD 快速迭代
什么是迭代呢,簡(jiǎn)單來說就是循環(huán) ,為什么要使用快速迭代呢,因?yàn)閮?nèi)部能開子線程,執(zhí)行速度快

/**
 參數(shù) 1 : 要遍歷的次數(shù)
 參數(shù) 2 : 隊(duì)列(只能傳并發(fā)隊(duì)列)
 參數(shù) 3 :index 索引
 
 */
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
    
    NSLog(@"-%zd---%@",index,[NSThread mainThread]);

});
    

12.GCD隊(duì)列組
隊(duì)列組的作用是會(huì)監(jiān)聽假如隊(duì)列組中任務(wù)的執(zhí)行情況

隊(duì)列組的兩種方式
a :

///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ù)
  /**
   封裝任務(wù)
   把任務(wù)添加到隊(duì)列中
   會(huì)監(jiān)聽任務(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]);
  });

  //攔截任務(wù),當(dāng)隊(duì)列組中所有的任務(wù)都執(zhí)行完畢的時(shí)候會(huì)進(jìn)入下面的方法
  dispatch_group_notify(group, queue, ^{
      NSLog(@"---完成所有任務(wù)---");
      
  });

b :

///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)聽范圍,進(jìn)入群組
  //dispatch_group_enter|dispatch_group_leave 必須要配對(duì)使用
  dispatch_group_enter(group);
  dispatch_async(queue, ^{
      NSLog(@"1---%@",[NSThread currentThread]);
      
      //離開群組
      dispatch_group_leave(group);
  });
  
  dispatch_group_enter(group);
  dispatch_async(queue, ^{
      NSLog(@"2---%@",[NSThread currentThread]);
      
      //離開群組
      dispatch_group_leave(group);
  });

  //攔截通知 內(nèi)部本身是異步的 ,不會(huì)堵塞
//    dispatch_group_notify(group, queue, ^{
//        NSLog(@"----完成任務(wù)----");
//    });
  
  //死等 直到隊(duì)列組中所有任務(wù)都執(zhí)行完畢之后才能執(zhí)行
  //本身是阻塞的,這一行代碼沒有執(zhí)行完畢后面代碼永遠(yuǎn)不會(huì)被執(zhí)行
  dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
  NSLog(@"---over");

13:GCD 隊(duì)列組實(shí)例 ,下載兩張圖片,合并圖片

/**
  1.下載圖片1 開子線程
  2.下載圖片2 開子線程
  3.合成圖片并顯示圖片 開子線程
  
  */
 
 
 //獲取一個(gè)隊(duì)列組
 
 dispatch_group_t group = dispatch_group_create();
 
 //獲得并發(fā)隊(duì)列
 dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
 
 //1.下載圖片 1 開啟子線程
 dispatch_group_async(group, queue,^{
     //確定url
     NSURL *url = [NSURL URLWithString:@""];
     //下載二進(jìn)制數(shù)據(jù)
     NSData *imageData = [NSData dataWithContentsOfURL:url];
 
     //轉(zhuǎn)換圖片
     self.image1 = [UIImage imageWithData:imageData];
     
     
 });
 
 
 
 
 //2.下載圖片2 開啟子線程
  dispatch_group_async(group, queue,^{
     //確定url
     NSURL *url = [NSURL URLWithString:@""];
     //下載二進(jìn)制數(shù)據(jù)
     NSData *imageData = [NSData dataWithContentsOfURL:url];
     
     //轉(zhuǎn)換圖片
    self.image2 = [UIImage imageWithData:imageData];
     
 });
 

 
 
 //3.合并圖片
 
  dispatch_group_notify(group, queue, ^{
      //1.1 創(chuàng)建圖形上下文
      UIGraphicsBeginImageContext(CGSizeMake(200, 200));
     
      //1.2 畫圖 圖片1
      [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
      self.image1 = nil;
      
      //1.3 畫圖 圖片 2
      [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
      self.image2 = nil;
      
      
      //1.4根據(jù)上下文得到一張圖片
 UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
      
      
      //1.5關(guān)閉上下文
      UIGraphicsEndImageContext();
      
      
      //1.6更新UI
      
      dispatch_async(dispatch_get_main_queue(), ^{
          self.imageView.image = image;
          
      });
      
  });

4.NSOperation

NSOperation 主要元素就是 操作和隊(duì)列,其實(shí)操作也就是任務(wù).
NSOperation 是一個(gè)抽象類,沒有封裝,所以我們使用的時(shí)候要使用其自子類,接下來詳細(xì)講解一下 .

  1. NSInvocationOperation

不會(huì)開線程,在主線程執(zhí)行

1.創(chuàng)建操作封裝任務(wù)
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(TZrun) object:nil];
2 啟動(dòng)執(zhí)行操作
  [op1 start];

2.NSBlockOperation

-(void)blockOperation{

   NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    
    NSLog(@"1----%@",[NSThread mainThread]);
    
}];
    

    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"2----%@",[NSThread mainThread]);
        
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"3----%@",[NSThread mainThread]);
        
    }];
    
    
    //追加任務(wù),如果一個(gè)操作中的任務(wù)數(shù)量大于1,那么會(huì)開子線程并發(fā)執(zhí)行任務(wù)
    [op3 addExecutionBlock:^{
        
        NSLog(@"4----%@",[NSThread mainThread]);

    }];
    
    [op3 addExecutionBlock:^{
        
        NSLog(@"5----%@",[NSThread mainThread]);

    }];
    
    [op3 addExecutionBlock:^{
        
        NSLog(@"6----%@",[NSThread mainThread]);

    }];
    
    
    //啟動(dòng)任務(wù)
    [op1 start];
    [op2 start];
    [op3 start];
    
    
}

3.NSOperationQueue 如何使用呢,我們要實(shí)現(xiàn)在子線程里面做事情.
首先我們先講一下GCD 中的隊(duì)列 和 NSOperation 中隊(duì)列的區(qū)別
GCD 中的隊(duì)列
串行隊(duì)列 :
a : 創(chuàng)建串行隊(duì)列
b : 主隊(duì)列

并行隊(duì)列 :
a : 創(chuàng)建并行隊(duì)列
b : 全局并發(fā)隊(duì)列

NSOperation中的隊(duì)列

主隊(duì)列 : [NSOperationQueue mainQueue] 和 GCD 主隊(duì)列一樣,串行隊(duì)列

非主隊(duì)列 : [[NSOperationQueue alloc]init];(同時(shí)具備串行和并發(fā)的功能) 默認(rèn)狀態(tài)下非主隊(duì)列是并發(fā)隊(duì)列

NSOperationQueue使用示例

-(void)invocationOperationWithQueue{


    //1.創(chuàng)建操作封裝任務(wù)
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(TZrun) object:nil];
     NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(TZrun) object:nil];
     NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(TZrun) object:nil];
    
    
  //2.創(chuàng)建隊(duì)列
  NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //3.添加操作到隊(duì)列
    [queue addOperation:op1];
    [queue addOperation:op2];   // 內(nèi)部已經(jīng)調(diào)用了 start 方法
    [queue addOperation:op3];
    
    
}
-(void)blockOperationWithQueue{
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"1----%@",[NSThread mainThread]);
        
    }];
    
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"2----%@",[NSThread mainThread]);
        
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"3----%@",[NSThread mainThread]);
        
    }];
    
    [op3 addExecutionBlock:^{
        
        NSLog(@"4----%@",[NSThread mainThread]);
        
    }];
    
    [op3 addExecutionBlock:^{
        
        NSLog(@"5----%@",[NSThread mainThread]);
        
    }];
    
    [op3 addExecutionBlock:^{
        
        NSLog(@"6----%@",[NSThread mainThread]);
        
    }];
    
     //2.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //3.添加操作到隊(duì)列
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    
    
    //簡(jiǎn)便方法
    //內(nèi)部 : 1創(chuàng)建操作 2.添加操作到隊(duì)列中
    
    [queue addOperationWithBlock:^{
        
        NSLog(@"7----%@",[NSThread mainThread]);

    }];
    

}

4.自定義 NSOperation 子類

系統(tǒng)為我們提供了子類 封裝操作,為什么我還要定義子類呢.
因?yàn)榫拖袷且粋€(gè)自定義View 一樣 我們不喜歡分散的寫在Controller 一樣,假如我們操作代碼十分龐大,方便我復(fù)用等等,我們需要自定義.

通過重寫 main 方法 來實(shí)現(xiàn)我們需要操作的事情

@interface TZOperation : NSOperation
#import "TZOperation.h"

@implementation TZOperation
//告知的是執(zhí)行什么任務(wù)
//有利于代碼隱蔽
//復(fù)用性好


-(void)main{
    NSLog(@"---封裝的操作");
    

}

@end
-(void)TZOperationWithQueue{
    //1.封裝操作
    TZOperation *op1 = [[TZOperation alloc]init];
    TZOperation *op2 = [[TZOperation alloc]init];
    
    //2.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //3.添加操作隊(duì)列
    [queue addOperation:op1];
    [queue addOperation:op2];
    
}

5.NSOperation 的依賴和監(jiān)聽

有時(shí)候我們同時(shí)執(zhí)行多個(gè)操作,但是我們要在某一個(gè)操作之后完成后再執(zhí)行另一個(gè)操作,這就用到了 依賴.

如何知道我們的某一操作已經(jīng)做完了-監(jiān)聽

注意點(diǎn) : 我們所添加的依賴不能循環(huán),那樣的話,兩個(gè)操作都不會(huì)執(zhí)行.

依賴可以跨隊(duì)列依賴

 //1.創(chuàng)建隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    NSOperationQueue *queue2 = [[NSOperationQueue alloc]init];
    
    
    //2.封裝操作
    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]);
        
    }];

    
    
    //操作監(jiān)聽
    
    op3.completionBlock = ^{
    
        NSLog(@"+++操作三完成了 ^_^ ");
        
    };
    
    
    
    //添加操作依賴
    
    //注意點(diǎn) : 不能循環(huán)依賴
    
    //可以跨隊(duì)列依賴
    [op1 addDependency:op4];
    [op4 addDependency:op3];
    [op3 addDependency:op2];
    
    //任務(wù)執(zhí)行順序
    // op2->op3->op4->op1
    
    
    //3添加操作
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    [queue2 addOperation:op4];

6.NSOperation 線程間的通信
和GCD 還有NSThread 一樣,我們做完子線程操再回到主線程操作.
我們做兩個(gè)例子,一個(gè)是下載圖片 和 合成圖片,來看看NSOperation 如何實(shí)現(xiàn)線程間的通信

a : 下載圖片

//下載圖片
-(void)TZdown{
   
   //1.開子線程下載圖片 非主隊(duì)列
   NSOperationQueue *queue = [[NSOperationQueue alloc]init];
   
   //2.封裝操作
   NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{
       
       
       NSURL *URL  = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
       
       NSData *imageData = [NSData dataWithContentsOfURL:URL];
       UIImage *imge = [UIImage imageWithData:imageData];
       
       
       //更新UI
       [[NSOperationQueue mainQueue] addOperationWithBlock:^{
           
           self.imageView.image = imge;
           
       }];
       
       
   }];
   
   //添加操作到隊(duì)列中
   [queue addOperation:download];

}

b : 合成圖片

  //1.開子線程下載圖片 非主隊(duì)列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    
    __block UIImage *image1;
    __block UIImage *image2;

    //2.封裝操作 下載圖片 1
    NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{
        
        
        NSURL *URL  = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
        
        NSData *imageData = [NSData dataWithContentsOfURL:URL];
        image1 = [UIImage imageWithData:imageData];
        
        
        
    }];
    
    //3.封裝操作,下載圖片2
    NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
        
        
        NSURL *URL  = [NSURL URLWithString:@"http://imgsrc.baidu.com/baike/pic/item/3c6d55fbb2fb4316fb41d3f525a4462308f7d3e7.jpg"];
        
        NSData *imageData = [NSData dataWithContentsOfURL:URL];
        image2 = [UIImage imageWithData:imageData];
        
        
    }];
    
    
    //4合并圖片的操縱
    
    //4.1
    NSBlockOperation *com = [NSBlockOperation blockOperationWithBlock:^{
        
       //開啟上下文
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        
       //畫圖1
        [image1 drawInRect:CGRectMake(0, 0, 100, 200)];
        
       //畫圖2
        [image2 drawInRect:CGRectMake(100, 0, 100, 200)];
        
       //根據(jù)上下文得到圖片
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        
        
       //關(guān)閉上下文
        UIGraphicsEndImageContext();
        
        //7.更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            
            self.imageView.image = image;
            
        }];
        
    }];
    
    
    //5.設(shè)置依賴關(guān)系
    [com addDependency:download];
    [com addDependency:download2];
    
    //6.添加操作到隊(duì)列里面去
    
    [queue addOperation:download];
    [queue addOperation:download2];
    [queue addOperation:com];

合成圖片 用到了 依賴 ,合成操作依賴于 兩個(gè)下載操作已經(jīng)完成

三 : 線程安全性

  • 原因
  1. 一塊資源可能被多個(gè)線程共享,也就是多個(gè)線程可能會(huì)訪問同一塊內(nèi)存資源.

  2. 比如多個(gè)線程訪問同一個(gè)對(duì)象,同一個(gè)變量,同一個(gè)文件

  3. 當(dāng)多個(gè)線程訪問同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂,和數(shù)據(jù)安全問題.

蘋果官方訪問統(tǒng)一資源造成了數(shù)據(jù)錯(cuò)亂示意圖


未加鎖.png
  • 解決辦法

    1. 互斥鎖:
      必須是全局唯一的,鎖定一份代碼只能用一把鎖,用多把鎖是無效的
加互斥鎖.png

上圖為加了互斥所 后 線程A 和線程 B 訪問同一塊資源,線程A 在讀取 17 后被鎖上 進(jìn)行 +1操作 變?yōu)?code>18 直到解鎖寫入 ,線程B 才能方位這個(gè)資源 否則不能訪問,當(dāng) 線程A 解鎖后,線程B 訪問的結(jié)果是18 再 對(duì)其進(jìn)行 +1操作 最后寫入結(jié)果為19 然后線程B 解鎖

  • 代碼

添加互斥所 只需要一行代碼 @synchronized就可以啦 以下代碼做使用示范

A,B,C 是三個(gè)手機(jī) 共同在網(wǎng)上賣電影票


    self.TZCount = 1000;
    self.theadA = [[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.theadA.name = @"A";
    self.threadB.name = @"B";
    self.threadC.name = @"C";
    
    //啟動(dòng)線程
    [self.theadA start];
    [self.threadB start];
    [self.threadC start];


-(void)saleTicket{
    
    while (1) {
       
        @synchronized (self) {
            NSInteger TZCount = self.TZCount;
            if (TZCount > 0) {
                [self lastTimeAction];
                self.TZCount = TZCount;
              
            }else{
                
                NSLog(@"出售光了");
                break;
            }
        }
       
    }
       

}

模擬耗時(shí)操作

-(void)lastTimeAction{

    for (int i = 0 ; i < 100000; i++) {
        
    }
}

@synchronized (self) 因?yàn)槿治ㄒ?我們通常使用self

  • 注意事項(xiàng)

    1. 注意加鎖的位置
    2. 注意加鎖的條件:多線程共享同一塊資源
    3. 注意加鎖是耗費(fèi)性能的
  • 互斥鎖的優(yōu)點(diǎn)
    能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題

  • 互斥所的缺點(diǎn)
    需要消耗大量CPU 資源

加互斥所又叫線程同步,多條線程在同一條線上按順序執(zhí)行

  • 原子性和非原子屬性
  1. atomic : 原子屬性為setter方法加鎖(默認(rèn)就atomic)
  2. nonatomic : 非原子屬性,不會(huì)為setter方法加鎖
  • 原子和非原子的對(duì)比
    atomic : 線程安全,需要消耗大量的資源
    nonatomic : 非線程安全,適合內(nèi)存較小的移動(dòng)設(shè)備

一般在開發(fā)中我們都是用 nonatomic 屬性
盡量避免多線程搶奪同一塊資源.

四: 總結(jié)

對(duì)于像GCD ,NSOperation 常用控制線程的 API 應(yīng)該熟練掌握
在實(shí)際開發(fā)應(yīng)用中我們大多用到的就是開設(shè)子線程

創(chuàng)建隊(duì)列
創(chuàng)建任務(wù)
任務(wù)放進(jìn)隊(duì)列里
根據(jù)需求 設(shè)置好同步異步任務(wù),和串行并行隊(duì)列
刷新主線程UI 等等操作. 希望大家讀完這一期有所收獲,下期再見 ^ _ ^

//----------希望我們永遠(yuǎn)年輕-----永遠(yuǎn)熱淚盈眶-----

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,814評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,224評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,444評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,804評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,998評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評(píng)論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,237評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評(píng)論 1 287
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,706評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,993評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容

  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴。。 不如我直接引用一個(gè)最簡(jiǎn)單的問題,以這個(gè)作為切入點(diǎn)好了 在ma...
    Mr_Baymax閱讀 2,810評(píng)論 1 17
  • 原文:http://www.cocoachina.com/ios/20170707/19769.html 本文主要...
    冬的天閱讀 2,305評(píng)論 0 12
  • 什么是進(jìn)程? 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序。 每個(gè)進(jìn)程之間是獨(dú)立的,每個(gè)進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存...
    珍此良辰閱讀 1,268評(píng)論 1 5
  • 進(jìn)程的概念 進(jìn)程是操作系統(tǒng)上的概念,操作系統(tǒng)是直接驅(qū)動(dòng)、管理計(jì)算機(jī)硬件的一款管理軟件,它的運(yùn)行幫助我們利用計(jì)算機(jī)硬...
    伶俐ll閱讀 732評(píng)論 0 3
  • 多線程 在iOS開發(fā)中為提高程序的運(yùn)行效率會(huì)將比較耗時(shí)的操作放在子線程中執(zhí)行,iOS系統(tǒng)進(jìn)程默認(rèn)啟動(dòng)一個(gè)主線程,用...
    郭豪豪閱讀 2,608評(píng)論 0 4