iOS多線程進階

.一.進程

  • 進程:是指在系統(tǒng)中正在運行的一個應(yīng)用程序,每個進程之間是獨立的,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi)

  • 比如同時打開迅雷、Xcode,系統(tǒng)就會分別啟動2個進程

二.線程

  • 1.什么是線程?
    答:1個進程要想執(zhí)行任務(wù),必須得有線程(每1個進程至少要有1條線程),一個進程(程序)的所有任務(wù)都在線程中執(zhí)行

  • 比如使用酷狗播放音樂、使用迅雷下載電影,都需要在線程中執(zhí)行

  • 2.線程的串行

  • <1>.1個線程中任務(wù)的執(zhí)行是串行的,如果要在1個線程中執(zhí)行多個任務(wù),那么只能一個一個地按順序執(zhí)行這些任務(wù),也就是說,在同一時間內(nèi),1個線程只能執(zhí)行1個任務(wù)

  • <2>.比如在1個線程中下載3個文件(分別是文件A、文件B、文件C)

  • 3.多線程

  • <1>什么是多線程?
    答:1個進程中可以開啟多條線程,每條線程可以并行(同時)執(zhí)行不同的任務(wù),多線程技術(shù)可以提高程序的執(zhí)行效率.

  • 比如下載文件:可以同時下載

  • <2>.多線程的原理

    多線程的原理
    (1).同一時間,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)
    (2).多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換)
    (3).如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象
    
  • 問題:如果線程非常非常多,會發(fā)生什么情況?

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

  • <3>.多線程的優(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)、??臻g(子線程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)用

  • (1).主線程

  • 1.什么是主線程?
    答:一個iOS程序運行后,默認會開啟1條線程,稱為“主線程”或“UI線程”

  • 2.主線程的主要作用

    • 顯示\刷新UI界面
    • 處理UI事件(比如點擊事件、滾動事件、拖拽事件等)
  • 3.主線程的使用注意

    • 別將比較耗時的操作放到主線程中
    • 耗時操作會卡住主線程,嚴(yán)重影響UI的流暢度,給用戶一種“卡”的壞體驗
  • 4.如果將耗時操作放在主線程:主線程的的UI無法更新,按鈕不可用,必須等耗時操作完成才有反應(yīng)

耗時操作放在主線程

耗時操作demo 密碼: qugm

  • 5.如果將耗時操作放在子線程(后臺線程、非主線程)

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

iOS中多線程的實現(xiàn)方案

五.對多線程開辟的詳細介紹

  • 1.pthread的詳細介紹

    • 1.導(dǎo)入: #import <pthread.h>

    • 2.開辟子線程(調(diào)用的是一個函數(shù))

      pthread_t thread;
      /*
         pthread 屬于POSIX 多線程開發(fā)框架
         參數(shù)
         1:指向線程的指針
         2:線程屬性
         3:指向函數(shù)的指針
         4:傳遞給函數(shù)的參數(shù)
      
         返回值:特別在C語言框架,非常常見
         如果是0,表示正確 noErr
         如果是非0,表示錯誤代碼
      
      
         void * (*)     (void *)
         void * demo    (void  *param)
         返回值  函數(shù)指針   參數(shù)
      
         void * 等價于 OC id
      */
      NSString *str = @"我是參數(shù)";
      int result = pthread_create(&thread, NULL, &run,(__bridge void *)(@"我是參數(shù)"));
      
      if (result == 1) {
           // 可以把1 換為 noErr
           NSLog(@"OK");
      }else{
      
           NSLog(@"error: %d",result);
      }
      
    • 3.調(diào)用子線程

       void *run(void *param)
      {
              for (NSInteger i = 0; i<50000; i++)
             {
               NSLog(@"-----buttonClick-----%zd",i);
             }
             return NULL;
       }
      

      pthread代碼

  • 2.NSThread

  • (1).一個NSThread對象就代表一條線程(這種創(chuàng)建方式可以拿到線程的對象以及可以對線程設(shè)置名字,以及獲取主線程)子線程執(zhí)行完任務(wù)就自動死亡

    創(chuàng)建、啟動線程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [thread start];
    //線程一啟動,就會在線程thread中執(zhí)行self的run方法
    
  • (2).主線程相關(guān)用法

    +(NSThread *)mainThread;// 獲得主線程
    
    -(BOOL)isMainThread;// 是否為主線程
    
    +(BOOL)isMainThread; // 是否為主線程
    
  • (3).其他用法

    • <1>獲得當(dāng)前線程

      NSThread *current = [NSThread currentThread];
      
    • <2>.線程的屬性:名字(name)(在項目開發(fā)中,可以根據(jù)這個來查找崩潰原因)

      - (void)setName:(NSString*)n;
      -(NSString *)name;
      
      NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
      thread. name = @"JK";
      [thread start];
      
    • <3>.線程的屬性:優(yōu)先級(threadPriority)

       /*
          優(yōu)先級別:0.0 - 1.0;0.5
          0.0:最低
          1.0:最高
      
          只是保證CPU的調(diào)度的可能性;
      
          多線程目的:將耗時操作放在后臺執(zhí)行
          建議:不要在開發(fā)過程,修改優(yōu)先級
        */
      NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
      thread. threadPriority = 1;
      [thread start];
      
  • (4).其他創(chuàng)建線程方式

    • <1>.創(chuàng)建線程后自動啟動線程

      [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
      
    • <2>.隱式創(chuàng)建并啟動線程(其實也就是在后臺創(chuàng)建子線程)

      [self performSelectorInBackground:@selector(run) withObject:nil];
      

    上述2種創(chuàng)建線程方式的優(yōu)缺點

  • 優(yōu)點:簡單快捷

  • 缺點:無法對線程進行更詳細的設(shè)置

NSThread的code 密碼: gsq4

  • (5).線程的狀態(tài)
線程的狀態(tài)
  • (6).控制線程狀態(tài)

    • <1>.啟動線程

       -(void)start;
      

      進入就緒狀態(tài) -> 運行狀態(tài)。當(dāng)線程任務(wù)執(zhí)行完畢,自動進入死亡狀態(tài)

    • <2>.阻塞(暫停)線程

      +(void)sleepUntilDate:(NSDate *)date;// 進入阻塞狀態(tài)
      
      +(void)sleepForTimeInterval:(NSTimeInterval)ti;// 進入阻塞狀態(tài)
      
    • <3>.強制停止線程

      +(void)exit; //進入死亡狀態(tài)  例如:  [NSThread exit];//線程強制退出
      

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

  • (7).多線程的安全隱患:資源共享

    <1>.1塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源

    <2>. 比如多個線程訪問同一個對象、同一個變量、同一個文件

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

第一個例子: 銀行取錢

銀行取錢

第二個例子: 售票

售票
安全問題分析
  • <4>.安全隱患解決– 互斥鎖 : 加鎖是消耗資源的

    安全解決方案

  • (1).互斥鎖使用格式

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

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

  • (3).互斥鎖的使用前提:多條線程搶奪同一塊資源

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

    • 線程同步的意思是:多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務(wù))

    • 互斥鎖,就是使用了線程同步技術(shù)

  • (5)、互斥鎖的使用范圍:盡量要小,范圍大,效率就會差。
    搶票資源的解決問題

    搶票資源的解決問題

賣票搶奪資源代碼

六.線程之間的通信

  • <1>.原子和非原子屬性
    OC在定義屬性時有nonatomicatomic兩種選擇

    • atomic:原子屬性,為setter方法加鎖(默認就是atomic

    • nonatomic:非原子屬性,不會為setter方法加鎖:避免資源消耗

  • <2>.原子非原子屬性的選擇

    • nonatomicatomic對比
    • atomic:線程安全,需要消耗大量的資源
    • nonatomic:非線程安全,適合內(nèi)存小的移動設(shè)備
  • <3>.iOS開發(fā)的建議

    • 所有屬性都聲明為nonatomic

    • 盡量避免多線程搶奪同一塊資源

    • 盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動客戶端的壓力

  • <4>.什么叫做線程間通信?

    答:在1個進程中,線程往往不是孤立存在的,多個線程之間需要經(jīng)常進行通信

  • <5>.線程間通信的體現(xiàn)

  • (1).1個線程傳遞數(shù)據(jù)給另1個線程

  • (2).在1個線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另1個線程繼續(xù)執(zhí)行任務(wù)

  • <6>.線程間通信常用方法

線程間通信常用方法

舉個例子:用UIImageView加載圖片

//1.獲取圖片的路徑
NSString *stringPath = @"http://h.hiphotos.baidu.com/image/h%3D360/sign=48771214b08f8c54fcd3c3290a282dee/c9fcc3cec3fdfc0375633742d03f8794a4c22635.jpg";
//開始時間(秒數(shù),從1970年0點0時0分0秒開始算起)
CFTimeInterval begin = CFAbsoluteTimeGetCurrent();

//2.根據(jù)圖片的路徑去下載圖片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringPath]];
//結(jié)束時間
CFTimeInterval end = CFAbsoluteTimeGetCurrent();

NSLog(@"開始時間=%f 結(jié)束時間=%f 消耗時間==%f",begin,end,end -begin);

//3.加載下載好的圖片
self.picture.image = [UIImage imageWithData:data];

獲取時間間隔的方式:

1.第一種獲取時間
NSDate *begin = [NSDate date];
//根據(jù)圖片的路徑去下載圖片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringPath]];
//結(jié)束時間
NSDate *end = [NSDate date];
NSLog(@"開始時間=%@ 結(jié)束時間=%@ 消耗時間==%f",begin,end,[end timeIntervalSinceDate:begin]);

2.第二種獲取時間
//開始時間(秒數(shù),從1970年0點0時0分0秒開始算起)
CFTimeInterval begin = CFAbsoluteTimeGetCurrent();
//2.根據(jù)圖片的路徑去下載圖片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringPath]];
//結(jié)束時間
CFTimeInterval end = CFAbsoluteTimeGetCurrent();
NSLog(@"開始時間=%f 結(jié)束時間=%f 消耗時間==%f",begin,end,end -begin);
  • <7>.子線程下載圖片,在主線程刷新UI

    1.跳轉(zhuǎn)刷新UI

    [self performSelectorOnMainThread:@selector(refreshUI:) withObject:image waitUntilDone:YES];
    
    /**
     *  刷新UI
     */
    -(void)refreshUI:(UIImage *)image
    {
        //3.加載下載好的圖片
       self.picture.image = image;
    }
    

    2.直接不跳轉(zhuǎn)刷新UI

    [self.picture performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
    

最后解釋一下方法的最后一個參數(shù)waitUntilDone:YES或者NO的問題

YES:意思是刷新UI之后再走子線程之后的其他任務(wù)
NO:意思是主線程刷新的同時,其他線程也在執(zhí)行任務(wù)

YES與NO的區(qū)別

線程通信方式的demo 密碼: 85x4

七.GCD

  • <1> 什么是GCD ?
    GCD全稱是Grand Central Dispatch,可譯為“牛逼的中樞調(diào)度器”,純C語言,提供了非常多強大的函數(shù)

  • <2>.GCD的優(yōu)勢

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

    • 任務(wù):執(zhí)行什么操作
    • 隊列:用來存放任務(wù)
  • <4>.GCD的使用就2個步驟

    • 1.定制任務(wù),確定想做的事情
    • 2.將任務(wù)添加到隊列中
    • GCD會自動將隊列中的任務(wù)取出,放到對應(yīng)的線程中執(zhí)行
    • 任務(wù)的取出遵循隊列的FIFO原則:先進先出,后進后出
  • <5>.執(zhí)行任務(wù)

    • GCD中有2個用來執(zhí)行任務(wù)的函數(shù)

      1.用同步的方式執(zhí)行任務(wù)(不具備開辟新線程的能力)
      dispatch_sync(dispatch_queue_t queue,dispatch_block_t block);
      queue:隊列
      block:任務(wù)

      2.用異步的方式執(zhí)行任務(wù)
      dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
      同步和異步的區(qū)別:
      同步:只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
      異步:可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力

  • <6>.GCD的隊列可以分為2大類型

    1.并發(fā)隊列(Concurrent Dispatch Queue

    • (1).可以讓多個任務(wù)并發(fā)(同時)執(zhí)行(自動開啟多個線程同時執(zhí)行任務(wù))

    • (2).并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效

    2.串行隊列(Serial Dispatch Queue

    • 讓任務(wù)一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))
  • <7>.有4個術(shù)語比較容易混淆:同步、異步、并發(fā)、串行

    • 1.同步和異步主要影響:能不能開啟新的線程

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

    • 2.并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式

      并發(fā):多個任務(wù)并發(fā)(同時)執(zhí)行
      串行:一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù)

  • <8>.并發(fā)隊列

    • GCD默認已經(jīng)提供了全局的并發(fā)隊列,供整個應(yīng)用使用,不需要手動創(chuàng)建

    • 使用dispatch_get_global_queue函數(shù)獲得全局的并發(fā)隊列
      dispatch_queue_priority_t priority // 隊列的優(yōu)先級 ,蘋果推薦使用 DISPATCH_QUEUE_PRIORITY_DEFAULT
      unsigned long flags //此參數(shù)暫時無用,用0即可
      dispatch_queue_t dispatch_get_global_queue( dispatch_queue_priority_t priority, unsigned long flags);

    • 獲得全局并發(fā)隊列

      dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
      
    • 全局并發(fā)隊列的優(yōu)先級

      #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
      #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(中)
      #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
      #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后
      
  • <9>.全局并發(fā)隊列與全局串行隊列獲取方式

    • 并發(fā)隊列:創(chuàng)建的2種方式
      獲得全局并發(fā)隊列

      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      獲取不是全局的并發(fā)隊列
      dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_CONCURRENT);
      
    • 串行隊列:創(chuàng)建的2種方式
      獲得全局串行隊列

      dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_SERIAL);
      dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL);
      dispatch_release(queue);非ARC需要釋放手動創(chuàng)建的隊列
      
  • <10>.主隊列

    • 主隊列(跟主線程相關(guān)聯(lián)的隊列)

    • 主隊列是GCD自帶的一種特殊的串行隊列

    • 放在主隊列中的任務(wù),都會放到主線程中執(zhí)行

    • 使用dispatch_get_main_queue()獲得主隊列

      dispatch_queue_t queue = dispatch_get_main_queue();
      
  • <11>.各種隊列的執(zhí)行效果

各種隊列的執(zhí)行效果

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

下面寫一個GCD線程阻塞的代碼

GCD線程阻塞的代碼
  • <12>.GCD主線程與子線程之間的通信

    從子線程回到主線程

    GCD主線程與子線程之間的通信

  • <13>.GCD里面還有一個來執(zhí)行任務(wù)的函數(shù):barrier是柵欄

    • barrier是柵欄的意思,在它前面的任務(wù)執(zhí)行完之后才執(zhí)行,而且后面的任務(wù)等它執(zhí)行結(jié)束后才去執(zhí)行.
  • 注意 這個隊列queue不能是全局的并發(fā)隊列

       dispatch_queue_t queue = dispatch_queue_create("12235", DISPATCH_QUEUE_CONCURRENT);
    
       dispatch_barrier_async(queue, ^{
    
           NSLog(@"您在執(zhí)行barrier");
    
        });
    

例如下面的代碼:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
     dispatch_queue_t queue = dispatch_queue_create("12235", DISPATCH_QUEUE_CONCURRENT);

     dispatch_async(queue, ^{
          NSLog(@"1111");
     });
     dispatch_async(queue, ^{
         NSLog(@"2222"); 
     });
    dispatch_barrier_async(queue, ^{
         NSLog(@"您在執(zhí)行barrier"); 
    });
    dispatch_async(queue, ^{
        NSLog(@"3333");  
    });
    dispatch_async(queue, ^{
        NSLog(@"4444");
    });
}

打印結(jié)果如下

打印結(jié)果如下

八.iOS常見的延時執(zhí)行有2種方式

  • <1>.調(diào)用NSObject的方法

     2秒后再調(diào)用self的run方法
    [self performSelector:@selector(run) withObject:nil afterDelay:2];
    
  • <2>.使用GCD函數(shù)
    2s后的代碼在哪里執(zhí)行是和隊列有關(guān)系的

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      
            NSLog(@"2s了");
    });
    
  • <3>.NSTimer定時器

    NO代表不重復(fù),定時器執(zhí)行完就進行銷毀,如果是YES就代表每隔2s調(diào)用一次run方法

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

九.一次性代碼

使用dispatch_once函數(shù)能保證某段代碼在程序運行過程中只被執(zhí)行1次

  static dispatch_once_t onceToken;
  dispatch_once(&onceToken,^{

     只執(zhí)行1次的代碼(這里面默認是線程安全的)

  });

提醒:一次性代碼慎用,它是程序在運行過程中只執(zhí)行一次

十.剪切利用多線程分析 (剪切是耗時的,要放到子線程里面)

剪切利用多線程分析
  • 1.傳統(tǒng)的剪切方式
#pragma mark 傳統(tǒng)的剪切文件
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSString *form = @"/Users/jinqianxiang/Desktop/Form";
    NSString *to = @"/Users/jinqianxiang/Desktop/To";
    
    /**
     *  創(chuàng)建文件管理對象
     */
    NSFileManager *filemanger = [NSFileManager defaultManager];
    //獲取要剪切的文件夾里面的每個對象名字
    NSArray *array = [filemanger subpathsAtPath:form];
    
    for (NSString *subpath in array) {
        //全路徑
        NSString *formPath = [form stringByAppendingPathComponent:subpath];
        NSString *toPath = [to stringByAppendingPathComponent:subpath];
      
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          
          //剪切(全路徑剪切)
          [filemanger moveItemAtPath:formPath  toPath:toPath error:nil];
       
        });
    }
}
  • 2.比較快的剪切方式

    -(void)apply
    {
       NSString *form = @"/Users/jinqianxiang/Desktop/Form";
       NSString *to = @"/Users/jinqianxiang/Desktop/To";
    
        /**
         *  創(chuàng)建文件管理對象
         */
       NSFileManager *filemanger = [NSFileManager defaultManager];
       //獲取要剪切的文件夾里面的每個對象名字
       NSArray *array = [filemanger subpathsAtPath:form];
    
       dispatch_apply(array.count, dispatch_get_global_queue(0, 0), ^(size_t index) {
      
      //NSLog(@"456--%@",[NSThread currentThread]);
      
      NSString *sunPath = array[index];
      //全路徑
      NSString *formPath = [form stringByAppendingPathComponent:sunPath];
      NSString *toPath = [to stringByAppendingPathComponent:sunPath];
    
      //剪切(全路徑剪切)
      [filemanger moveItemAtPath:formPath  toPath:toPath error:nil];
      
      NSLog(@"---%@---%zd",[NSThread currentThread],index);
      
      });
    }
    
比較快的剪切方式

文件的剪切demo 密碼: 7edp

十一.GCD隊列組和柵欄(barrier)一樣的效果:確定線程的優(yōu)先級順序

  • 有這么1種需求?
  • 首先:分別異步執(zhí)行2個耗時的操作
  • 其次:等2個異步操作都執(zhí)行完畢后,再回到主線程執(zhí)行操作

如果想要快速高效地實現(xiàn)上述需求,可以考慮用隊列組

dispatch_group_t group =  dispatch_group_create();
// 1.
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{

        //執(zhí)行1個耗時的異步操作

});
//2.
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{

      //執(zhí)行1個耗時的異步操作

});
//3.
dispatch_group_notify(group,dispatch_get_main_queue(),^{

     //等前面的異步操作都執(zhí)行完畢后,回到主線程...

});

這樣就是1和2執(zhí)行完了才會執(zhí)行3

下面以兩張圖片合成為例:

兩張圖片合成為例

兩張圖片合成demo 密碼: 3w5f

關(guān)鍵性代碼:

 /**
 *  3.將兩個圖片合成
 */
dispatch_group_notify(group, queue, ^{
    /**
     *  開啟新的圖形上下文
     */
    UIGraphicsBeginImageContext(CGSizeMake(200, 200));
    
    //繪制圖片
    [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
    //繪制圖片
    [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
    
    /**
     *  取得上下文里面的圖片
     */
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    
    /**
     *  結(jié)束上下文
     */
    UIGraphicsEndImageContext();
    
    /**
     *  4.進入主隊列加載合成的圖片
     */
    
    dispatch_async(dispatch_get_main_queue(), ^{
        
        self.imageView.image = image;
        
  });

十二.GCD單例模式

  • 1.單例模式的作用

    • 可以保證在程序運行過程,一個類只有一個實例,而且該實例易于供外界訪問

    • 從而方便地控制了實例個數(shù),并節(jié)約系統(tǒng)資源

  • 2.單例模式的使用場合

    • 在整個應(yīng)用程序中,共享一份資源(這份資源只需要創(chuàng)建初始化1次)
  • 3.單例模式在ARC\MRC環(huán)境下的寫法有所不同,需要編寫2套不同的代碼

    • 可以用宏判斷是否為ARC環(huán)境
 #if __has_feature(objc_arc)
 //ARC
 #else
 //MRC
 #endif
  • 4.GCD單例模式1(比較簡單)
#import "PersonMessages.h"

static PersonMessages *personMessages;
@implementation PersonMessages

 +(instancetype)allocWithZone:(struct _NSZone *)zone
 {

     static dispatch_once_t onceToken;

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

  return personMessages;
}
@end




說明:不管是alloc還是allocWithZone 都會調(diào)用allocWithZone

  • 5.完整的一個單例(粒)對象的.m包含3個部分

下面還是以PersonMessages類為例
.h里面

#import <Foundation/Foundation.h>

@interface PersonMessages : NSObject

+(instancetype)sharePersonMessages;

@end

.m里面

#import "PersonMessages.h"
@implementation PersonMessages
static PersonMessages *personMessages;
/**
 *  1.保證調(diào)用 [ PersonMessages sharePersonMessages] 只會調(diào)用一次init
 *
 *  @return personMessages
 */
+(instancetype)sharePersonMessages
{
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
    
    personMessages = [[self alloc]init];
    
    });

  return personMessages;
}

/**
 *  2.外面調(diào)用n次訪問同一個對象
 *
 *  @return personMessages
 */
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
   static dispatch_once_t onceToken;

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

   return personMessages;
}

/**
 *  3.外面進行copy的時候還是同一個對象
 *
 *  @return personMessages;
 */
 -(id)copy
{
    return personMessages;
}
@end
完整的單例(粒)

解釋一下:

1.static:意思防止外面人訪問更改

static PersonMessages *personMessages;

2.once默認是安全的(已經(jīng)加鎖), onceToken是來記錄訪問過block

dispatch_once(&onceToken, ^{

        personMessages = [super allocWithZone:zone];

    });
  • 6.對單例(粒)的一種封裝,只需要傳類名

下面我對單例類進行封裝解釋

對單例類進行封裝

GCD單例的封裝 密碼: yxan
傳統(tǒng)單例的封裝 密碼: xxne

使用方法:圖中3步

.h

1

.m

2

十三.NSOperation

  • <1>.NSOperation的作用

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

  • <2>.NSOperation和NSOperationQueue實現(xiàn)多線程的具體步驟

    • (1).先將需要執(zhí)行的操作封裝到一個NSOperation對象中
    • (2).然后將NSOperation對象添加到NSOperationQueue
    • (3).系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來
    • (4).將取出的NSOperation封裝的操作放到一條新線程中執(zhí)行

注意:

  • <3>.NSOperation的子類

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

  • <4>.使用NSOperation子類的方式有3種

    • (1).NSInvocationOperation

    • (2).NSBlockOperation

    • (3).自定義子類繼承NSOperation,實現(xiàn)內(nèi)部相應(yīng)的方法

  • <5>.NSInvocationOperation

  • 創(chuàng)建NSInvocationOperation對象
    -(id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

  • 2.調(diào)用start方法開始執(zhí)行操作

    -(void)start;
    一旦執(zhí)行操作,就會調(diào)用target的sel方法

  • 3.注意:

    默認情況下,調(diào)用了start方法后并不會開一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作

    只有將NSOperation放到一個NSOperationQueue中,才會異步執(zhí)行操作(也就是進入子線程)

NSInvocationOperation
  • <6>.NSBlockOperation

** 一張圖**


NSBlockOperation
NSOperationQueue的作用

- 如果將`NSOperation`添加到`NSOperationQueue`(操作隊列)中,系統(tǒng)會**自動異步執(zhí)行**NSOperation中的操作:相當(dāng)于`start`

另外還可以直接用隊列添加Operation

      /**
        *  1.創(chuàng)建隊列
        */
       NSOperationQueue *operationQueue = [NSOperationQueue new];

      /**
       *  特殊情況,隊列直接添加operation(添加任務(wù))
       */
          [operationQueue addOperationWithBlock:^{
    
             NSLog(@"%@",[NSThread currentThread]);
         }];
  • <7>.自定義創(chuàng)建NSOperation
1.自定義創(chuàng)建NSOperation
2.自定義創(chuàng)建NSOperation

注意:放到隊列之后,它會自動開啟start

NSOperation創(chuàng)建的3中形式 密碼: qaex

  • <8>.最大并發(fā)數(shù)

    • 并發(fā)數(shù)是指:同時執(zhí)行的任務(wù)數(shù),比如,同時開3個線程執(zhí)行3個任務(wù),并發(fā)數(shù)就是3
  • 最大并發(fā)數(shù)也就是NSOperationQueue的屬性
    maxConcurrentOperationCount,當(dāng)=1時就是串行隊列了

     /**
      *  1.創(chuàng)建隊列
      */
      NSOperationQueue *operationQueue = [NSOperationQueue new];
    
     /**
      *  2.設(shè)置最大并發(fā)數(shù)
     */
      operationQueue.maxConcurrentOperationCount = 1;
    

    最大并發(fā)數(shù)只要大于1,是幾就代表幾條線程同時執(zhí)行,執(zhí)行完的線程有可能會被重復(fù)利用

最大并發(fā)數(shù) 密碼: xxwk

  • <9>.隊列的取消暫停、恢復(fù)(操作對象是:隊列 NSOperationQueue)

    • 取消隊列的所有操作
      -(void)cancelAllOperations;
      提示:也可以調(diào)用NSOperation的-(void)cancel方法取消單個操作

提示:在自定義的NSOperation的-(void)main{}方法里面,如果線程取消了,我們應(yīng)該進行判斷,防止性能消耗

取消隊列的所有操作
  • 暫停和恢復(fù)隊列

    @property (getter=isSuspended) BOOL suspended;
    -(void)setSuspended:(BOOL)b;// YES代表暫停隊列,NO代表恢復(fù)隊列
    -(BOOL)isSuspended;
    

舉個例子:

暫?;蛘呷∠犃?/div>

NSOperationQueue的掛起和取消 密碼: 58is

  • <10>.操作依賴

    注意:依賴必須添加到NSOperation對象添加到隊列之前進行依賴

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

    • 比如一定要讓操作A執(zhí)行完后,才能執(zhí)行操作B,可以這么寫

      [operationB addDependency:operationA];// 操作B依賴于操作A
      
    • 可以在不同queue的NSOperation之間創(chuàng)建依賴關(guān)系(添加的是NSOperation的對象,不管是不是在同一個隊列:也可以說能夠跨隊列)

跨隊列
  • <11>.操作的監(jiān)聽
    可以監(jiān)聽一個操作的執(zhí)行完畢

    -(void(^)(void))completionBlock;
    -(void)setCompletionBlock:(void(^)(void))block;
    
可以監(jiān)聽一個操作的執(zhí)行完畢

最后:提一下圖片的緩存機制

  • 內(nèi)存緩存

  • 沙盒緩存

  • 硬盤緩存

    /**
     *  1.內(nèi)存緩存圖片
     */
    @property(nonatomic,strong) NSMutableDictionary *dataDictionary;
    
    NSString *stringImage = [NSString stringWithFormat:@"%@",self.plist[indexPath.row]];
    
    //先從內(nèi)存緩存中取出圖片
    UIImage *image = self.dataDictionary[stringImage];
    
    if (image) {//內(nèi)存有圖片
    
    cell.imageCellPicture.image = image;
    
    }else{// 內(nèi)存中沒有圖片
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
    //獲取Library/Caches 文件夾
    NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
     //獲取文件名(也就是取得路徑的最后一個路徑)
     NSString *fileName = [stringImage lastPathComponent];
     //計算出全路徑
     NSString *filePath = [cachesPath stringByAppendingPathComponent:fileName];
     //取出圖片
     NSData *data = [NSData dataWithContentsOfFile:filePath];
    
    if(data){//直接利用沙盒中圖片
       
       dispatch_async(dispatch_get_main_queue(), ^{
           
           cell.imageCellPicture.image = [UIImage imageWithData:data];
           
       });
        //存到字典中
       self.dataDictionary[stringImage] = cell.imageCellPicture.image;
       
     }else
    {   // 下載圖片
       data = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringImage]];
       
       UIImage *image1 = [UIImage imageWithData:data];
       
       dispatch_async(dispatch_get_main_queue(), ^{
           
           cell.imageCellPicture.image = image1;
       });
       
       // 存到字典中
       self.dataDictionary[stringImage] = cell.imageCellPicture.image;
       // 將圖片文件數(shù)據(jù)寫入沙盒中
       [data writeToFile:filePath atomically:YES];
      }
     });
    }
    

圖片緩存代碼<不完善> 密碼: wjhv

沙盒緩存

完善的圖片緩存 密碼: gr3k

無沙盒緩存

最后再總結(jié)一下隊列類型

GCD 的隊列類型

  • 1.并發(fā)隊列

    • 自己創(chuàng)建的

    • 全局

      并發(fā)隊列:創(chuàng)建的2種方式

      獲得全局并發(fā)隊列
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      獲取不是全局的并發(fā)隊列
      dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_CONCURRENT);
      
  • 2.串行隊列

    • 主隊列

    • 自己創(chuàng)建的

      串行隊列:創(chuàng)建的2種方式
      
      獲得全局串行隊列
      dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_SERIAL);
      dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL);
      dispatch_release(queue);非ARC需要釋放手動創(chuàng)建的隊列
      

NSOperationQueue隊列類型

  • 主隊列

    • [NSOperationQueue mainQueue];
    • 凡是添加到主隊列中的任務(wù)NSOperation,都會到主線程中執(zhí)行
  • 非主隊列(其他隊列)

    • [[NSOperationQueue alloc]init];
    • 同時包含了:串行,并發(fā)功能
    • 添加到這種隊列中的任務(wù)(NSOperation),就會自動放到主線程中執(zhí)行
最后編輯于
?著作權(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)容