iOS 多線程 淺述

什么是進程?

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

什么是線程?


  • 1個進程要想執(zhí)行任務(wù),必須得有線程(每1個進程至少要有1條線程)。
  • 線程是進程的基本執(zhí)行單元,一個進程(程序)的所有任務(wù)都在線程中執(zhí)行。

小拓展

- 線程的串行(就像烤串一樣)
    - 1個線程中任務(wù)的執(zhí)行是串行的。
    - 如果要在1個線程中執(zhí)行多個任務(wù),那么只能一個一個地按順序執(zhí)行這些任務(wù)。
    - 在`同一時間內(nèi)`,1個線程只能執(zhí)行1個任務(wù)。

什么是多線程?

  • 1個進程中可以開啟多條線程,每條線程可以并行(同時)執(zhí)行不同的任務(wù)。

  • 線程的并行(同時執(zhí)行)

    • 比如同時開啟3條線程分別下載3個文件(分別是文件A、文件B、文件C。
  • 多線程并發(fā)執(zhí)行的原理:

    • 在同一時間里,CPU只能處理1條線程,只有1條線程在工作(執(zhí)行)。
    • 多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換),如果CPU調(diào)度線程的時間足夠快,就造成了多線程并發(fā)執(zhí)行的假象。(如下圖)
    CPU調(diào)用線程

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

  • 優(yōu)點
    • 能適當(dāng)提高程序的執(zhí)行效率。
    • 能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
  • 缺點
    • 開啟線程需要占用一定的內(nèi)存空間(默認情況下,主線程占用1M,子線程占用512KB),如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能。
    • 線程越多,CPU在調(diào)度線程上的開銷就越大。
    • 程序設(shè)計更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享

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

- 主線程
    - 一個iOS程序運行后,默認會在自己的進程中開啟1條線程,稱為“主線程”也叫“UI線程”。
    - 作用:刷新顯示UI,處理UI事件。
- 使用注意
    - 不要將耗時操作放到主線程中去處理,因為會卡住主線程,造成UI卡頓(用戶體驗差)。
    - 和UI相關(guān)的刷新操作`必須`放到主線程中進行處理。

線程的狀態(tài)

  • 線程的各種狀態(tài):新建-就緒-運行-阻塞-死亡
  • 常用的控制線程狀態(tài)的方法
        [NSThread exit];//退出當(dāng)前線程
        [NSThread sleepForTimeInterval:7.0];//阻塞線程
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:7.0]];//阻塞線程
    
    

    注意:線程死亡后不能復(fù)生


線程安全:

  • 前提:多個線程同時訪問同一塊資源會發(fā)生數(shù)據(jù)安全問題 解決方案:加互斥鎖
  • 相關(guān)代碼:@synchronized(self){}
  • 專業(yè)術(shù)語-線程同步
  • 原子和非原子屬性(是否對setter方法加鎖)

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

方案 簡介 語言 線程生命周期 使用頻率
pthread 一套通用的多線程API
(跨平臺\可移植)
C語言 程序員管理 幾乎不用
NSThread 使用更加面向?qū)ο?
(簡單易用,可直接操作線程對象)
OC語言 程序員管理 偶爾使用
GCD 為了替代NSThread為生
充分利用設(shè)備多核
C語言 系統(tǒng)自動管理 經(jīng)常使用
NSOperation 基于GCD
更加面向?qū)ο?更方便地設(shè)置線程之間的依賴 監(jiān)聽線程狀態(tài)KVO
OC語言 系統(tǒng)自動管理 經(jīng)常使用

pthread簡單使用

1.包含頭文件(必須)

#import <pthread.h>

2.創(chuàng)建線程

//  創(chuàng)建線程
/**
     *
     * 參數(shù)一:線程對象(傳地址)
     * 參數(shù)二:線程的屬性(名稱\優(yōu)先級)
     * 參數(shù)三:只想函數(shù)的指針
     * 參數(shù)四:函數(shù)需要接受的字符串參數(shù),可以不傳遞(注:由于我們創(chuàng)建的是OC的字符串,所以在傳值的時候需要將其轉(zhuǎn)換成C的字符串)
     */
    pthread_t thread;
    NSString *num = @"123";
    pthread_create(&thread, NULL, task, (__bridge void *)(num));

3.定義參數(shù)所需要的函數(shù)指針


void *task(void *num)
{
    NSLog(@"當(dāng)前線程 -- %@,傳入的參數(shù):-- %@", [NSThread currentThread], num);

    return NULL;
}

如果需要退出線程的話只需調(diào)用下面代碼

pthread_exit(NULL);

運行結(jié)果:


pthread線程使用截圖

NSThread簡單使用

這邊介紹NSThread創(chuàng)建線程的4種方式:

  • 第一種 (alloc nitWithTarget:selector:object:)
    • 特點:需要手動開啟線程,可以拿到線程對象進行詳細設(shè)置
    • 優(yōu)缺點:
      • 缺點:需要手動開啟線程執(zhí)行任務(wù)
      • 優(yōu)點:可以拿到線程對象
//  創(chuàng)建線程
    /**
     * 參數(shù)一:目標對象
     * 參數(shù)二:方法選擇器(線程啟動后調(diào)用的方法)
     * 參數(shù)三:調(diào)用方法需要接受的參數(shù)
     */
    NSThread *thread = [[NSThread alloc] initWithTarget:self
                                               selector:@selector(task)
                                                 object:nil];

    //  開始執(zhí)行
    [thread start];
  • 第二種(分離出一條子線程)
    • 特點:自動啟動線程,無法對線程進行更詳細的設(shè)置
    • 優(yōu)缺點:
      • 缺點:無法拿到線程對象 進行更詳細設(shè)置
      • 優(yōu)點:代碼簡單且自動開啟線程執(zhí)行
//  創(chuàng)建線程
    /**
     * 參數(shù)一:要調(diào)用的方法
     * 參數(shù)二:目標對象 self
     * 參數(shù)三:調(diào)用方法需傳遞的參數(shù)
     */
    [NSThread detachNewThreadSelector:@selector(task)
                             toTarget:self
                           withObject:nil];
  • 第三種(后臺線程)
    • 特點:自動啟動線程,無法進行更詳細設(shè)置
    • 優(yōu)缺點:
      • 缺點:無法拿到線程對象 進行更詳細設(shè)置
      • 優(yōu)點:代碼簡單且自動開啟線程執(zhí)行

/**
 *  NSThread創(chuàng)建一條后臺線程
 */
- (void)nsthreadTest3
{
    //  創(chuàng)建線程
    /**
     * 參數(shù)一:要調(diào)用的方法
     * 參數(shù)二:調(diào)用方法需傳遞的參數(shù)
     */
    [self performSelectorInBackground:@selector(run:) withObject:@"后臺線程"];

}

- (void)run:(NSString *)str
{
    NSLog(@"當(dāng)前線程:%@ -- 接收到的參數(shù):%@", [NSThread currentThread], str);
}

  • 第四種(自定義NSThread類并重寫內(nèi)部的方法實現(xiàn))
    • 特點:可以不暴露一些實現(xiàn)細節(jié),使代碼增加隱蔽性。(一般出現(xiàn)在第三方框架內(nèi))
    • 優(yōu)缺點:
      • 缺點:繁瑣,且需要手動開啟線程執(zhí)行
      • 優(yōu)點:增加代碼隱蔽性

1.創(chuàng)建自定義類繼承自NSThread
2.重寫NSThread類中的main方法

- (void)main
{
    NSLog(@"當(dāng)前線程--%@", [NSThread currentThread]);
}

3.創(chuàng)建線程對象

/**
 *  NSThread創(chuàng)建一條后臺線程
 */
- (void)nsthreadTest4
{
    //  創(chuàng)建線程
    SJThread *thread = [[SJThread alloc] init];

    //  開啟執(zhí)行
    [thread start];
}

線程間通信

有時候我們會從服務(wù)器上下載圖片然后再展示出來,下載的操作我們會放到子線程,而UI刷新的操作只能在主線程中執(zhí)行。這樣就涉及到線程間的通信。接下來我們分三種方式來簡單實現(xiàn)一下:

  • 方式一:
- (void)viewDidLoad {
    [super viewDidLoad];

    // 開啟一條線程下載圖片
    [NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];

}


- (void)downloadImage
{
    //  網(wǎng)絡(luò)圖片url
    NSURL *url = [NSURL URLWithString:@"http://img3.imgtn.bdimg.com/it/u=3841157212,2135341815&fm=206&gp=0.jpg"];
    //  根據(jù)url下載圖片數(shù)據(jù)到本地
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    //  把下載到本地的二進制數(shù)據(jù)轉(zhuǎn)成圖片
    UIImage *image = [UIImage imageWithData:imageData];
    //  回到主線程刷新UI
    //  第一種方式
    [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];

    //  第二種方式
    //  直接調(diào)用iconView里面的setImage:方法就可以實現(xiàn)刷新
//    [self.iconView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];

    //  第三種方式
    //  此方法可以方便自由在主線程和其它線程切換
//    [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}

- (void)showImage:(UIImage *)image
{
    self.iconView.image = image;
}


GCD簡單使用

什么是GCD

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

GCD優(yōu)勢

  • GCD是蘋果公司為多核的并行運算提出的解決方案
  • GCD會自動利用更多的CPU內(nèi)核
  • GCD會自動關(guān)了線程生命周期(創(chuàng)建、調(diào)度、銷毀線程)
  • GCD性能很好(接近底層)

GCD的組合方式

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

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

GCD的任務(wù)和隊列

  • 任務(wù):執(zhí)行什么操作
  • 隊列:用來存放任務(wù)(GCD中提供了2種隊列)
    • 串行隊列
    • 并發(fā)隊列

GCD的使用

  • 定制任務(wù) —— 確定需要做的操作
  • 將任務(wù)添加到隊列中
  • GCD會自動將隊列中的任務(wù)取出,存放到線程中執(zhí)行
  • 任務(wù)的取出遵循隊列的FIFO原則(先進先出,后進后出)
FIFO原則圖片

GCD創(chuàng)建線程

  • 接下來看看同步函數(shù)和異步函數(shù)有什么區(qū)別:

1.先來看看異步并發(fā)隊列

- (void)test
{
    /**
     * 參數(shù)一:C語言的字符串,給隊列起一個名字或標識
     * 參數(shù)二:隊列類型
        DISPATCH_QUEUE_CONCURRENT   并發(fā)
        DISPATCH_QUEUE_SERIAL   串行
     */
    dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);

    /**
     *  使用函數(shù)封裝任務(wù)
     * 參數(shù)一:獲取隊列
     * 參數(shù)二:需要執(zhí)行的任務(wù)
     */
    dispatch_async(queue, ^{
        NSLog(@"在:%@線程執(zhí)行了任務(wù)",[NSThread currentThread]);
    });

    NSLog(@"結(jié)束");
}

執(zhí)行結(jié)果:


異步并發(fā)隊列截圖

2.再來看看同步并發(fā)隊列

- (void)test
{
    /**
     * 參數(shù)一:C語言的字符串,給隊列起一個名字或標識
     * 參數(shù)二:隊列類型
        DISPATCH_QUEUE_CONCURRENT   并發(fā)
        DISPATCH_QUEUE_SERIAL   串行(串行隊列可以用NULL表示)
     */
    dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);

    /**
     *  使用函數(shù)封裝任務(wù)
     * 參數(shù)一:獲取隊列
     * 參數(shù)二:需要執(zhí)行的任務(wù)
     */
    dispatch_sync(queue, ^{
        NSLog(@"在:%@線程執(zhí)行了任務(wù)",[NSThread currentThread]);
    });

    NSLog(@"結(jié)束");
}

執(zhí)行結(jié)果:


同步并發(fā)隊列截圖

結(jié)論:

從上面的2個運行結(jié)果的時間可以看出
1.異步并發(fā)隊列,會開啟一條子線程來處理任務(wù),以達到主線程和子線程同時執(zhí)行的并發(fā)效果。
2.同步并發(fā)隊列,不會開線程,必須等block塊中的代碼先執(zhí)行完畢才會繼續(xù)執(zhí)行以外的任務(wù),所以并發(fā)隊列對于同步函數(shù)來說等同于“無效”

  • 再看看并發(fā)隊列對異步函數(shù)和同步函數(shù)的影響:

1.同步函數(shù)+并發(fā)隊列

dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(queue, ^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    });

執(zhí)行結(jié)果:同步函數(shù)+并發(fā)隊列沒有開啟子線程的能力

并發(fā)隊列對同步函數(shù)的影響

2.異步函數(shù)+并發(fā)隊列

- (void)test2
{
    dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:異步函數(shù)+并發(fā)隊列會自動開啟3條子線程執(zhí)行任務(wù)

并發(fā)隊列對異步函數(shù)的影響

結(jié)論:

從上面可以看出,異步函數(shù)擁有開啟子線程的能力,而同步函數(shù)沒有開啟子線程的能力。


  • GCD中,除了并發(fā)隊列外,還有串行隊列,我們來看看如果把并發(fā)隊列換成串行隊列會有怎樣的變化

1.同步函數(shù)+串行隊列

- (void)test2
{
    dispatch_queue_t queue = dispatch_queue_create("串行隊列", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(queue, ^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:進一步證明同步函數(shù)沒有開啟子線程的能力,他的所有任務(wù)都在主線程中執(zhí)行

同步函數(shù)+串行隊列

2.異步函數(shù)+串行隊列

- (void)test2
{
    dispatch_queue_t queue = dispatch_queue_create("串行隊列", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"當(dāng)前線程:%@",[NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:開啟了一條子線程,在子線程中依次執(zhí)行任務(wù)

異步函數(shù)+串行隊列

結(jié)論

1.在同步函數(shù)+串行隊列中,任務(wù)依舊是在主線程中執(zhí)行。

2.在異步函數(shù)+串行隊列中,會自動開啟一條子線程,在子線程中依次執(zhí)行任務(wù)

3.再一次證明同步函數(shù)沒有開啟子線程的能力


系統(tǒng)提供的4個全局并發(fā)隊列

  • 在iOS中系統(tǒng)默認給我們提供了4個全局并發(fā)隊列
- (void)test3
{
    //  獲取全局并發(fā)隊列
    //  系統(tǒng)內(nèi)部默認提供4個全局并發(fā)隊列
    /**
     * 參數(shù)一:優(yōu)先級
     * 參數(shù)二:時間(傳0即可)
     */
//優(yōu)先級:DISPATCH_QUEUE_PRIORITY_HIGH 2
//      DISPATCH_QUEUE_PRIORITY_DEFAULT 0
//      DISPATCH_QUEUE_PRIORITY_LOW (-2)
//      DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN    級別最低

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(queue, ^{
        NSLog(@"1當(dāng)前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"2當(dāng)前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"3當(dāng)前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"4當(dāng)前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"5當(dāng)前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"6當(dāng)前線程:%@", [NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:在結(jié)果中我們看到GCD創(chuàng)建了6條線程,但是實際上GCD創(chuàng)建多少條線程完全由系統(tǒng)當(dāng)前情況而定,我們是無法控制的。

獲取全局隊列

特殊的串行隊列 —— 主隊列(與主線程相關(guān)聯(lián)的隊列)

  • 主隊列是GCD自帶的一種特殊的串行隊列
  • 放在主隊列中的人物,都會放到主線程中執(zhí)行
  • 使用dispatch_get_main_queue()的方式可獲取主隊列
    • 特點
      • 1.放在主隊列中的任務(wù),必須在主線程中執(zhí)行
      • 2.主隊列執(zhí)行任務(wù)的時候,在調(diào)度任務(wù)的時候,會先調(diào)用主線程的狀態(tài),如果當(dāng)前有任務(wù)在做,則會等待主線程執(zhí)行完任務(wù)再執(zhí)行自己的任務(wù)

1.主隊列+異步函數(shù)

- (void)test4
{
    //  獲取主隊列
    dispatch_queue_t queue = dispatch_get_main_queue();

    //  添加任務(wù)
    dispatch_async(queue, ^{
        NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:任務(wù)都在主線程中執(zhí)行

主隊列+異步函數(shù)

2.同步函數(shù)+主隊列

- (void)test4
{
    //  獲取主隊列
    dispatch_queue_t queue = dispatch_get_main_queue();

    //  添加任務(wù)
    dispatch_sync(queue, ^{
        NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:
進入死鎖狀態(tài),因為主隊列執(zhí)行任務(wù)的時候,在調(diào)度任務(wù)的時候,會先調(diào)用主線程的狀態(tài),如果當(dāng)前有任務(wù)在做,則會等待主線程執(zhí)行完任務(wù)再執(zhí)行自己的任務(wù)

如果要解決以上的情況,那么可以將任務(wù)添加到子線程中,這樣就不會出現(xiàn)死鎖的情況,程序也就能夠正常執(zhí)行了


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

- (void)test4
{
    //  獲取主隊列
    dispatch_queue_t queue = dispatch_get_main_queue();

    //  添加任務(wù)
    dispatch_sync(queue, ^{
        NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:

死鎖解決方式

總結(jié)

函數(shù)類型 并發(fā)隊列 手動創(chuàng)建的串行隊列 主隊列
同步 (sync) 1.沒有開啟新線程
2.串行執(zhí)行任務(wù)
1.有開啟新線程
2.串行執(zhí)行任務(wù)
死鎖
異步(async) 1.有開啟新線程
2.并發(fā)執(zhí)行任務(wù)
1.有開啟新線程
2.串行執(zhí)行任務(wù)
1.沒有開啟新線程
2.串行執(zhí)行任務(wù)

注意

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

GCD線程間的通信

  • 有時候我們需要在子線程進行一些耗時操作,等耗時操作完成后再回到主線程進行相應(yīng)的UI刷新,那么就可以使用下面的方式在子線程和主線程之間進行通信

- (void)test5
{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    dispatch_async(queue, ^{

        NSLog(@"在%@線程中執(zhí)行任務(wù)", [NSThread currentThread]);
        //  回到主線程
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"在%@線程中執(zhí)行任務(wù)", [NSThread currentThread]);
        });
    });
}

執(zhí)行結(jié)果:


線程間通信

GCD延遲執(zhí)行

  • 特點:可以選擇在哪個線程中執(zhí)行任務(wù)
- (void)test6
{
    NSLog(@"方法開始運行");
    /**
     *  GCD延遲執(zhí)行方法
     *
     *  參數(shù)一: 要延遲的時間 (以秒為單位)
     *  參數(shù)二: 在哪個線程中執(zhí)行
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"GCD定時器");
    });
}

執(zhí)行結(jié)果:

GCD延遲執(zhí)行

一次性代碼

  • 特點:
    • 能保證整個程序運行過程中,block內(nèi)的代碼塊只會被執(zhí)行一次
    • 線程是安全的
    • 應(yīng)用:簡單的單例模式(單例模式實現(xiàn)點我)
    • 注意點:不可放在懶加載中

- (void)test8
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"一次性代碼運行");
    });
}


柵欄函數(shù)

  • 作用:能夠控制并發(fā)隊列里面任務(wù)的執(zhí)行順序
  • 注意:不能使用全局并發(fā)隊列(會沒有任何區(qū)別,文檔中有注釋——只對自己創(chuàng)建的并發(fā)隊列有效)
- (void)test7
{
    //  創(chuàng)建隊列
    dispatch_queue_t queue = dispatch_queue_create(0, 0);

    dispatch_async(queue, ^{

        for (int i = 0; i<5; i++) {
            NSLog(@"1");
        }
    });
    dispatch_async(queue, ^{

        for (int i = 0; i<5; i++) {
            NSLog(@"2");
        }
    });

    dispatch_barrier_async(queue, ^{
        NSLog(@"進入柵欄函數(shù)");
    });

    dispatch_async(queue, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"3");
        }
    });

    dispatch_async(queue, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"4");
        }
    });
}

執(zhí)行結(jié)果:

柵欄函數(shù)執(zhí)行結(jié)果

GCD迭代開發(fā)(遍歷)

  • 一般我們傳統(tǒng)的遍歷方式如下,它的缺點就是在處理比較耗時的操作時效率較低,因為只在一個線程中執(zhí)行任務(wù)。
    //  傳統(tǒng)的遍歷方式
    for (int i ; i< 10; i++) {
        NSLog(@"%d -- 當(dāng)前線程%@", i, [NSThread currentThread]);
    }

執(zhí)行結(jié)果:


這里寫圖片描述
  • 在GCD中,為我們提供了一個迭代函數(shù),可以開啟子線程快速進行遍歷,這樣就可以大大提高效率,而且使用非常簡單。接下來使用迭代函數(shù)來進行文件復(fù)制的操作:
- (void)test9
{
    //  獲得文件原始路徑(上層文件夾得路徑)
    NSString *fromPath = @"/Users/yeshaojian/Desktop/test";

    //  獲得文件的目標路徑
    NSString *toPath = @"/Users/yeshaojian/Desktop/test2";

    //  得到文件路徑下面的所有文件
    NSArray *subpaths =  [[NSFileManager defaultManager] subpathsAtPath:fromPath];
    NSLog(@"文件名:%@",subpaths);

    //  獲取數(shù)組中文件的個數(shù)
    NSInteger count = subpaths.count;

    //  將要迭代的操作放到迭代函數(shù)內(nèi)
    dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t index){
            //  拼接需要復(fù)制的文件的全路徑
            NSString *fromFullpath = [fromPath stringByAppendingPathComponent:subpaths[index]];
            //  拼接目標目錄的全路徑
            NSString *toFullpath = [toPath stringByAppendingPathComponent:subpaths[index]];
            //  執(zhí)行文件剪切操作
            /*
             * 參數(shù)一:文件在哪里的全路徑
             * 參數(shù)二:文件要被剪切到哪里的全路徑
             */
            [[NSFileManager defaultManager] moveItemAtPath:fromFullpath toPath:toFullpath error:nil];

           NSLog(@"拼接需要復(fù)制的文件的全路徑:%@ -- 拼接目標目錄的全路徑:%@ -- 當(dāng)前線程:%@",fromFullpath,toFullpath,[NSThread currentThread]);
       });

}

執(zhí)行結(jié)果:


GCD迭代截圖

隊列組

  • 假如開發(fā)中有多個任務(wù),要求在所有任務(wù)都在子線程中并發(fā)執(zhí)行,且不能使用柵欄函數(shù),當(dāng)所有任務(wù)都執(zhí)行完成后打印“完成”。這樣的需求就需要用到GCD中的隊列組。
  • 應(yīng)用場合:
    • 對多個任務(wù)有強制依賴性,缺一不可時使用

1.隊列組的基本使用

- (void)test10
{
    // 獲取隊列組,用來管理隊列
    dispatch_group_t group = dispatch_group_create();

    //  獲取并發(fā)隊列
    dispatch_queue_t queue = dispatch_queue_create("cs", DISPATCH_QUEUE_CONCURRENT);

    //  添加任務(wù)
    dispatch_group_async(group, queue, ^{
        NSLog(@"cs1---%@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"cs2---%@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"cs3---%@", [NSThread currentThread]);
    });

    //  攔截通知:當(dāng)隊列組中所有的任務(wù)都執(zhí)行完畢后,會調(diào)用下面方法的block塊
    dispatch_group_notify(group, queue, ^{
        NSLog(@"完成");
    });
}

執(zhí)行結(jié)果:


隊列組截圖

隊列組函數(shù)內(nèi)部操作簡要流程

處理流程:
1.封裝任務(wù)
2.把任務(wù)提交到隊列
3.把當(dāng)前任務(wù)的執(zhí)行情況納入到隊列注的監(jiān)聽范圍

注意:下面方法本身是異步的
dispatch_group_notify(group, queue, ^{

    });

拓展:
在一些框架或者早期項目中,可能會見到下面2種隊列組的使用方法,在這邊順帶提及一下,但不推薦使用,因為太過繁瑣。

第一種

- (void)test11
{
    //  獲得隊列組,管理隊列
    dispatch_group_t group = dispatch_group_create();

    //  獲得并發(fā)隊列
    dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);

    //  表示開始把后面的異步任務(wù)納入到監(jiān)聽范圍
    //dispatch_group_enter & dispatch_group_leave
    dispatch_group_enter(group);

    //  使用異步函數(shù)封裝任務(wù)
    dispatch_async(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);

        //  通知隊列組該任務(wù)已經(jīng)執(zhí)行完畢
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

    //  攔截通知
    dispatch_group_notify(group, queue, ^{
        NSLog(@"--完成---");
    });
}

第二種


- (void)test11
{
    //  獲得隊列組,管理隊列
    dispatch_group_t group = dispatch_group_create();

    //  獲得并發(fā)隊列
    dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);

    //  表示開始把后面的異步任務(wù)納入到監(jiān)聽范圍
    //dispatch_group_enter & dispatch_group_leave
    dispatch_group_enter(group);

    //  使用異步函數(shù)封裝任務(wù)
    dispatch_async(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);

        //  通知隊列組該任務(wù)已經(jīng)執(zhí)行完畢
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"2---%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

    //  等待DISPATCH_TIME_FOREVER 死等,一直要等到所有的任務(wù)都執(zhí)行完畢之后才會繼續(xù)往下執(zhí)行
    //  同步執(zhí)行
    dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 0.00001 * NSEC_PER_SEC);

    //  等待timer m的時間 不管隊列中的任務(wù)有沒有執(zhí)行完畢都繼續(xù)往下執(zhí)行,如果在該時間內(nèi)所有事任務(wù)都執(zhí)行完畢了那么會返回一個0,否則是非0值
    long n =  dispatch_group_wait(group, timer);
    NSLog(@"%ld",n);

    NSLog(@"--完成---");
}


補充:同步\異步函數(shù)另一種創(chuàng)建方式

  • 其實同步函數(shù)和異步函數(shù)還有另外的創(chuàng)建方式,但是使用起來比較不方便,所以上面就沒提及,想想還是補充一下好了

1.異步函數(shù)(創(chuàng)建一個使用函數(shù)封裝代碼的異步函數(shù))

- (void)test12
{
    /**
     *  參數(shù)一:隊列
     *  參數(shù)二:要傳給函數(shù)的參數(shù)
     *  參數(shù)三:函數(shù)
     */
    dispatch_async_f(dispatch_get_global_queue(0, 0), NULL, testTask);
}

void testTask(void *param)
{
    NSLog(@"%@", [NSThread currentThread]);
}

2.同步函數(shù)(創(chuàng)建一個使用函數(shù)封裝代碼的同步函數(shù))

- (void)test12
{
    /**
     *  參數(shù)一:隊列
     *  參數(shù)二:要傳給函數(shù)的參數(shù)
     *  參數(shù)三:函數(shù)
     */
    dispatch_sync_f(dispatch_get_global_queue(0, 0), NULL, testTask);
}

void testTask(void *param)
{
    NSLog(@"%@", [NSThread currentThread]);
}

上面使用的是函數(shù)來封裝要處理的代碼,使用比較不方便,且block是輕量級的數(shù)據(jù)結(jié)構(gòu),更推薦使用block封裝代碼的形式創(chuàng)建同步\異步函數(shù)。

GCD一些需要注意的細節(jié)

  • 全局并發(fā)隊列是默認存在的(在我們程序運行的時候就存在)
  • 全局隊列根據(jù)隊列的優(yōu)先級分為 (高,默認,低,后臺優(yōu)先級)4個并發(fā)隊列
  • iOS 6之前,我們通過創(chuàng)建的線程,是要自己手動施放的
    • 施放的方式 —— dispatch_release()
  • 使用柵欄函數(shù),蘋果官方文檔明確規(guī)定柵欄函數(shù)只有在和使用create函數(shù)創(chuàng)建的筆法隊列一起使用才有效
  • 暫時就想到這么多O(∩_∩)O,因為GCD已經(jīng)開源,想研究的朋友可以到網(wǎng)上搜索一下,有哪里不對的可以聯(lián)系我,謝謝!

NSOperation簡單使用

NSOperation作用

  • 配合使用NSOperation和NSOperationQueue也能實現(xiàn)多線程編程

NSOperation和NSOperationQueue實現(xiàn)多線程的具體步驟

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

NSOperation的子類

  • NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類
  • 使用NSOperation子類的方式有3種
    • NSInvocationOperation
    • NSBlockOperation
    • 自定義子類繼承NSOperation,實現(xiàn)內(nèi)部相應(yīng)的方法

NSOperation封裝操作

  • 第一種方式 —— NSInvocationOperation
- (void)invocationTest
{
    /**
     * 參數(shù)一:目標對象
     * 參數(shù)二:調(diào)用方法
     */
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
    
    //  開啟任務(wù)
    [op1 start];
}

- (void)download
{
    NSLog(@"下載:%@",[NSThread currentThread]);
}

執(zhí)行結(jié)果:需要和隊列并用才會開啟子線程執(zhí)行任務(wù)


NSInvocationOperation
  • 第二種方式 —— Block
- (void)blockTest
{
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載:%@",[NSThread currentThread]);
    }];
    
    [op1 addExecutionBlock:^{
        NSLog(@"增加的下載:%@", [NSThread currentThread]);
    }];
    
    //  開啟任務(wù)
    [op1 start];
    [op2 start];
    [op3 start];
}

- (void)download
{
    NSLog(@"下載:%@",[NSThread currentThread]);
}

執(zhí)行結(jié)果:如果一條線程中執(zhí)行的操作大于1就會開啟新線程并發(fā)執(zhí)行


NSBlockOperation
  • 方式三 —— 自定義NSOperation

1.先創(chuàng)建一個繼承自NSOperation的類并重寫main方法


- (void)main
{
    NSLog(@"當(dāng)前線程:%@", [NSThread currentThread]);
}

2.在需要使用的類中引用自定義的類,并創(chuàng)建開啟任務(wù)

- (void)custom
{
    SJOperation *op1 = [[SJOperation alloc] init];
    
    [op1 start];
}

執(zhí)行結(jié)果:需要手動開啟線程或者與隊列并用才會開啟子線程


自定義NSOperation

NSOperation中的隊列

  • 主隊列 (獲取方式:+mainQueue)
    • 所有在主隊列中的任務(wù)都在主線程中執(zhí)行
    • 本質(zhì)上是串行隊列
- (void)invocationQueue
{
    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];
    
    //  獲取主隊列
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    
}

執(zhí)行結(jié)果:所有任務(wù)都在主隊列中執(zhí)行,且是串行隊列

NSInvocationOperation在主隊列使用情況
  • 非主隊列(獲取方式:alloc init)
    • 同時具備并發(fā)和串行功能
    • 默認下是并發(fā)的
- (void)invocationQueue
{
    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];
    
    //  獲取非主隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    
}

執(zhí)行結(jié)果:所有任務(wù)在子線程中并發(fā)執(zhí)行

NSInvocationOperation在非主隊列使用情況

注意:addOperation:內(nèi)部已經(jīng)幫我們執(zhí)行了開啟任務(wù)方法,所有不需要另外實現(xiàn)。


NSBlockOperation與隊列并用的簡單寫法

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載:%@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載:%@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載:%@",[NSThread currentThread]);
    }];

執(zhí)行結(jié)果:所有任務(wù)都在子線程中并發(fā)執(zhí)行

NSBlockOperation在隊列中的簡單寫法

設(shè)置最大并發(fā)數(shù)

  • 在NSOperation中,我們要想控制串行隊列或者并發(fā)隊列,只需要設(shè)置maxConcurrentOperationCount屬性即可
    • 一般我們要使用串行隊列,只需設(shè)置值為1即可
    • 如果值大于1,則為并發(fā)隊列

1.串行隊列示例

- (void)blockQueue
{
    //  創(chuàng)建非主隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //  設(shè)置最大并發(fā)數(shù)為1,則隊列為串行隊列
    queue.maxConcurrentOperationCount = 1;
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載1:%@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載2:%@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載3:%@",[NSThread currentThread]);
    }];
    
}

執(zhí)行結(jié)果:按照任務(wù)添加順序執(zhí)行,所以是串行隊列


NSOperationQueue串行執(zhí)行

2.并發(fā)隊列示例

- (void)blockQueue
{
    //  創(chuàng)建非主隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //  設(shè)置最大并發(fā)數(shù)為6,一般子線程控制在6以內(nèi),太多線程會使設(shè)備壓力過大
    queue.maxConcurrentOperationCount = 6;
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載1:%@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載2:%@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載3:%@",[NSThread currentThread]);
    }];
    
}

執(zhí)行結(jié)果:程序并沒有按照添加順序完成任務(wù),所以是并發(fā)執(zhí)行

NSOperationQueue并發(fā)執(zhí)行

注意:

  1. 一般子線程控制在6以內(nèi),太多線程會使設(shè)備壓力過大
  2. maxConcurrentOperationCount默認值為-1(在計算機中,-1一般指最大值)
  3. 如果將maxConcurrentOperationCount設(shè)置為0,說明同一時間內(nèi)執(zhí)行0個任務(wù),所以任務(wù)將不會執(zhí)行。
maxConcurrentOperationCount系統(tǒng)默認值

NSOperation暫停、恢復(fù)和取消功能

  • 在NSOperation中,已經(jīng)為我們提供了暫停、恢復(fù)和取消的功能,我們只需調(diào)用相應(yīng)的方法即可。

1.暫停

    //  暫停
    [queue setSuspended:YES];

2.恢復(fù)

    //  取消
    [queue setSuspended:NO];

3.取消

    //  取消隊列中所有操作,且取消后的任務(wù)不可恢復(fù)
    [queue cancelAllOperations];

注意:

1.隊列中的的任務(wù)是有狀態(tài)的,分別是 —— 等待;執(zhí)行;完成三種狀態(tài),且暫停、恢復(fù)和取消操作并不能作用于當(dāng)前正處于執(zhí)行狀態(tài)的任務(wù),只能作用于等待狀態(tài)的任務(wù)。
2.如果是自定義的NSOperation,會發(fā)現(xiàn)暫停、恢復(fù)操作對其無效,對于這種情況,可以用以下方式解決 —— 使用取消操作

- (void)main
{
    //  模擬耗時操作
    for (int i = 0; i< 200; i++) {
        NSLog(@"1當(dāng)前線程:%@", [NSThread currentThread]);
    }
    //  判斷當(dāng)前狀態(tài),如果已經(jīng)取消,直接返回
    if (self.cancelled) return;
    
    //  模擬耗時操作
    for (int i = 0; i< 200; i++) {
        NSLog(@"2當(dāng)前線程:%@", [NSThread currentThread]);
    }
    //  判斷當(dāng)前狀態(tài),如果已經(jīng)取消,直接返回
    if (self.cancelled) return;
    
    //  模擬耗時操作
    for (int i = 0; i< 200; i++) {
        NSLog(@"3當(dāng)前線程:%@", [NSThread currentThread]);
    }
    //  判斷當(dāng)前狀態(tài),如果已經(jīng)取消,直接返回
    if (self.cancelled) return;
}

解決問題思路:其實這是蘋果官方文檔中的建議 —— 因為,當(dāng)我們調(diào)用cancelAllOperations:方法的時候,他內(nèi)部的cancelled屬性就會為真,每執(zhí)行完一個耗時操作后都進行一次判斷,如果發(fā)現(xiàn)已經(jīng)取消,則退出執(zhí)行。如果想更精確操控的話,也可以將判斷操作放到耗時操作中,但是不建議這樣做,因為這樣性能極差。


NSOperation中的依賴操作

  • NSOperation提供了一套非常便捷好用的操作依賴方式,比起GCD,那種酸爽簡直不敢相信
- (void)blockQueue
{
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載1:%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載2:%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載3:%@",[NSThread currentThread]);
    }];
    
    //  設(shè)置依賴關(guān)系
    //  op1依賴op2,只有當(dāng)op2執(zhí)行完畢后,才會執(zhí)行op1
    [op1 addDependency:op2];
    //  op2依賴op3,只有當(dāng)op3執(zhí)行完畢后,才會執(zhí)行op2
    [op2 addDependency:op3];
    
    //  獲取主隊列
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];

執(zhí)行結(jié)果:先執(zhí)行完op3,等op3執(zhí)行完成后才執(zhí)行op2,當(dāng)op2執(zhí)行完畢后,才執(zhí)行op1

NSOperation操作依賴

注意

  • NSOperation提供的操作依賴功能特別強大,可以設(shè)置不同隊列的依賴
  • 但是不能循環(huán)依賴,比如op1依賴op2,op2又依賴op1,而且并不會報錯,但會發(fā)生死鎖,且有關(guān)任務(wù)都不執(zhí)行。

NSOperation的監(jiān)聽

  • 我們經(jīng)常有這樣的需要:在某些任務(wù)執(zhí)行完成后,再執(zhí)行指定的某些操作,那么NSOperation中的監(jiān)聽功能就派上用場了,使用非常簡單
    NSOperation *op = [[NSOperation alloc] init];
    
    op.completionBlock = ^{
        NSLog(@"下載完成");
    };

    [op start];

NSOperation線程間通信

  • NSOperation線程間的通信類似于GCD,所以就不多敘述了,直接上代碼
- (void)downloadPhoto
{
    //  獲取非主隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //  創(chuàng)建下載任務(wù)
    [queue addOperationWithBlock:^{
       
        //  圖片地址
        NSURL *url = [NSURL URLWithString:@"http://cdn.duitang.com/uploads/item/201512/05/20151205092106_aksZU.jpeg"];
        //  下載圖片
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        //  轉(zhuǎn)換圖片
        UIImage *image = [UIImage imageWithData:imageData];
        //  回到主線程刷新
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            
            NSLog(@"回%@線程刷新UI", [NSThread currentThread]);
            
            self.imageView.image = image;
        }];
        
    }];
}

執(zhí)行結(jié)果:

NSOperation線程間通信.gif

GCD和NSOperation區(qū)別

在開發(fā)中最常用的就是GCD和NSOperation來進行多線程開發(fā),NSThread更多是在測試時輔助使用,pthread則很少看見,這里為大家簡單整理一下他們之間的區(qū)別

  • GCD和NSOperation的對比

    • GCD是純C語言的API,而操作隊列(NSOperation)則是Object-C的對象
    • 在GCD中,任務(wù)用Block塊來表示,而塊是輕量級的數(shù)據(jù)結(jié)構(gòu),相反,操作隊列(NSOperation)中的操作NSOperation是比較重量級的Object-C對象
  • 那么在開發(fā)中如何選擇呢?

    • 一般如果任務(wù)之間有依賴關(guān)系或者需要監(jiān)聽任務(wù)執(zhí)行的過程(KVO),首選NSOperation
    • 單純進行一些耗時操作則選用GCD,因為相比NSOperation,GCD效率更高,性能更好
  • NSOperation和NSOperationQueue好處

    • NSOperation可以方便設(shè)置操作優(yōu)先級(表示操作在隊列中與其它操作之間的優(yōu)先關(guān)系,級別越高越先執(zhí)行)
    • NSOperation可以通過KVO的方式對NSOperation對象進行監(jiān)聽控制(監(jiān)聽當(dāng)前操作是處于完成,取消還是執(zhí)行狀態(tài))
    • NSOperation可以方便設(shè)置操作之間的依賴關(guān)系
    • 通過自定義NSOperation子類可以實現(xiàn)操作復(fù)用
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 原文:http://www.cocoachina.com/ios/20170707/19769.html 本文主要...
    冬的天閱讀 2,329評論 0 12
  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴。。 不如我直接引用一個最簡單的問題,以這個作為切入點好了 在ma...
    Mr_Baymax閱讀 2,829評論 1 17
  • 多線程 在iOS開發(fā)中為提高程序的運行效率會將比較耗時的操作放在子線程中執(zhí)行,iOS系統(tǒng)進程默認啟動一個主線程,用...
    郭豪豪閱讀 2,617評論 0 4
  • 歡迎大家指出文章中需要改正或者需要補充的地方,我會及時更新,非常感謝。 一. 多線程基礎(chǔ) 1. 進程 進程是指在系...
    xx_cc閱讀 7,236評論 11 70
  • Object C中創(chuàng)建線程的方法是什么?如果在主線程中執(zhí)行代碼,方法是什么?如果想延時執(zhí)行代碼、方法又是什么? 1...
    AlanGe閱讀 1,792評論 0 17