iOS---多線程GCD

一于樟、什么是 GCD

1. GCD 是蘋果為解決多線程而定義的一套庫阱洪,并且 GCD 可以自動管理線程的生命周期,就和 ARC 類似槽奕,不需要我們手動去管理
2. GCD 是用 純C 語言 寫的嘴纺,所以我門使用的是 GCD 中的函數(shù)败晴,并不是面向?qū)ο蟮姆椒?/h5>
3. GCD 核心概念

1)任務(wù) : 就是某個線程要執(zhí)行的方法
  2)隊列 : 存放所有的任務(wù)

4. GCD 使用步驟

1)確定要執(zhí)行的任務(wù)
  2)將任務(wù)添加到隊列中,GCD 會自動將隊列中的任務(wù)取出栽渴,放在對應(yīng)的線程中去執(zhí)行

5. 同步異步

1)同步 : 在同一個線程中執(zhí)行任務(wù)尖坤,不會創(chuàng)建新的線程

// 同步函數(shù)
// 參數(shù) 1: 隊列
// 參數(shù) 2: 任務(wù)的代碼塊
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);

2)異步 : 創(chuàng)建一個新的線程,并在新的線程中執(zhí)行任務(wù)

// 異步函數(shù)
// 參數(shù) 1: 隊列
// 參數(shù) 2: 任務(wù)的代碼塊
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
6. 隊列

隊列可分為兩種
  1)異步隊列 : 即并行執(zhí)行的隊列闲擦,隊列中的每個任務(wù)都可以并發(fā)(同步)執(zhí)行
  2)串行隊列 : 即串行執(zhí)行的隊列慢味,隊列中的每個任務(wù)需要串行執(zhí)行,即一個一個來
獲得隊列

// 創(chuàng)建串行隊列
// 參數(shù) 1: 隊列名稱佛致,C風(fēng)格字符串
// 參數(shù) 2: 隊列的屬性贮缕,一般用 NULL 即可
dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);

dispatch_queue_t 是 GCD 中隊列的類型
// 獲得主隊列辙谜,主隊列是一個串行隊列俺榆,并且與主線程對應(yīng),主隊列中的任務(wù)都會被主線程執(zhí)行 dispatch_queue_t dispatch_get_main_queue(void);

// 全局并發(fā)隊列装哆,可以供整個應(yīng)用使用罐脊,不需手動創(chuàng)建
// 參數(shù) 1: 隊列的優(yōu)先級(有4個)
//    #define DISPATCH_QUEUE_PRIORITY_HIGH 2       高優(yōu)先級
//    #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0  默認(rèn)
//    #define DISPATCH_QUEUE_PRIORITY_LOW (-2)    低優(yōu)先級
// 參數(shù) 2: 隊列的屬性,可以穿 0
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

二蜕琴、GCD 基礎(chǔ)應(yīng)用

1. 異步/同步函數(shù) 與 串行/并行隊列

1)使用異步函數(shù)向并發(fā)隊列中添加任務(wù)

// 1. 打印主線程
NSLog(@"主線程 --- %@", [NSThread currentThread]);

// 2. 獲取全局并發(fā)隊列萍桌,并設(shè)置優(yōu)先級為默認(rèn)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
// 3. 添加任務(wù)到并行隊列中,就可以執(zhí)行任務(wù)了
// 使用異步函數(shù)添加任務(wù)凌简,可以開啟新的線程
dispatch_async(queue, ^{
    
    NSLog(@"任務(wù) 1 --- %@", [NSThread currentThread]);
    
});
    
dispatch_async(queue, ^{
        
    NSLog(@"任務(wù) 2 --- %@", [NSThread currentThread]);
        
});
    
    
dispatch_async(queue, ^{
        
    NSLog(@"任務(wù) 3 --- %@", [NSThread currentThread]);
        
});

運行結(jié)果


1.png

總結(jié) : 可以看出上炎,除了主線程之外,還分別創(chuàng)建了三個子線程雏搂,并且三個子線程是并發(fā)執(zhí)行的
2)使用異步函數(shù)向串行隊列中添加任務(wù)

// 1. 創(chuàng)建串行隊列
// 參數(shù) 1: 串行隊列的名稱藕施,是 C風(fēng)格字符串
// 參數(shù) 2: 串行隊列的屬性,一般來說串行隊列是不需要任何屬性凸郑,可以傳 NULL
dispatch_queue_t queue = dispatch_queue_create("Chuanxin", NULL);
    
NSLog(@"主線程 --- %@", [NSThread currentThread]);
    
// 2. 使用異步函數(shù)往串行隊列中添加任務(wù)
dispatch_async(queue, ^{
        
    NSLog(@"任務(wù) 1 --- %@", [NSThread currentThread]);
        
});
    
dispatch_async(queue, ^{
        
    NSLog(@"任務(wù) 2 --- %@", [NSThread currentThread]);
        
});
    
dispatch_async(queue, ^{
        
    NSLog(@"任務(wù) 3 --- %@", [NSThread currentThread]);
        
});

運行結(jié)果


2.png

總結(jié) : 使用異步函數(shù)向串行隊列中添加任務(wù)時裳食,會開啟新的線程,但是只會開啟一個芙沥;因為串行隊列中的任務(wù)需要一個一個執(zhí)行诲祸,不必同時執(zhí)行浊吏,所以只會創(chuàng)建一個新新線程
3)使用同步函數(shù)向并行隊列中添加任務(wù)

NSLog(@"主線程 --- %@", [NSThread currentThread]);

// 1. 獲取全局的并行隊列,并設(shè)置優(yōu)先級為 默認(rèn)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
// 2. 使用同步函數(shù)往并行隊列中添加任務(wù)
dispatch_sync(queue, ^{
        
    NSLog(@"任務(wù)1 --- %@", [NSThread currentThread]);
        
});
    
dispatch_sync(queue, ^{
    
    NSLog(@"任務(wù)2 --- %@", [NSThread currentThread]);
    
});
    
dispatch_sync(queue, ^{
        
    NSLog(@"任務(wù)3 --- %@", [NSThread currentThread]);
        
});

運行結(jié)果


3.png

總結(jié) : 因為使用的是同步函數(shù)救氯,所以不會創(chuàng)建新的線程找田,所以都是在主線程中執(zhí)行;此時径密,并發(fā)隊列就失去了其功能午阵,因為都沒有新的線程創(chuàng)建,何談并發(fā)
4)使用同步函數(shù)向串行隊列中添加任務(wù)

NSLog(@"主線程 --- %@", [NSThread currentThread]);
    
// 1. 創(chuàng)建串行隊列
dispatch_queue_t queue = dispatch_queue_create("Chuanxing", NULL);
    
// 2. 使用同步函數(shù)往串行隊列中添加任務(wù)
dispatch_sync(queue, ^{
        
    NSLog(@"任務(wù)1 --- %@", [NSThread currentThread]);
        
});
    
dispatch_sync(queue, ^{
        
    NSLog(@"任務(wù)2 --- %@", [NSThread currentThread]);
        
});
    
dispatch_sync(queue, ^{
        
    NSLog(@"任務(wù)2 --- %@", [NSThread currentThread]);
        
});

運行結(jié)果


4.png

總結(jié) : 因為使用的是同步函數(shù)享扔,所以不會創(chuàng)建新線程底桂,所以都在主線程中執(zhí)行,并且是在串行隊列中惧眠,所以任務(wù)會一個一個執(zhí)行

2. 主隊列 與 同步/異步

1)使用異步函數(shù)向主隊列添加任務(wù)

// 1. 獲取主線程
NSLog(@"mainThread --- %@", [NSThread currentThread]);
    
// 1. 獲取主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
// 2. 使用異步函數(shù)向主隊列中添加任務(wù)
dispatch_async(mainQueue, ^{
        
    NSLog(@"任務(wù) 1 --- %@", [NSThread currentThread]);
        
});
    
dispatch_async(mainQueue, ^{
    
    NSLog(@"任務(wù) 2 --- %@", [NSThread currentThread]);
    
});
    
dispatch_async(mainQueue, ^{
        
    NSLog(@"任務(wù) 3 --- %@", [NSThread currentThread]);
        
});

運行結(jié)果


5.png

總結(jié) : 雖然使用異步函數(shù)籽懦,但是卻向主隊列中添加任務(wù),所以不會創(chuàng)建新的線程氛魁,都在主隊列中執(zhí)行任務(wù)暮顺,并且由于主隊列是串行隊列,所以任務(wù)會一個一個執(zhí)行
2)使用同步函數(shù)向主隊列中添加任務(wù)

// 1. 獲取主線程
NSLog(@"mainThread --- %@", [NSThread currentThread]);
    
// 2. 獲取主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
// 3. 使用同步方式向主隊列中添加任務(wù)
dispatch_sync(mainQueue, ^{
        
    NSLog(@"任務(wù) 1 --- %@", [NSThread currentThread]);
        
});
    
dispatch_sync(mainQueue, ^{
        
    NSLog(@"任務(wù) 2 --- %@", [NSThread currentThread]);
        
});
    
dispatch_sync(mainQueue, ^{
        
    NSLog(@"任務(wù) 2 --- %@", [NSThread currentThread]);
        
});

運行結(jié)果
  使用同步函數(shù)向主隊列添加任務(wù)時會使程序崩潰
  例如上述代碼秀存,當(dāng)把 “任務(wù)1” 添加到主隊列時捶码,主隊列變會讓主線程執(zhí)行該任務(wù),但是此時主線程正在執(zhí)行該同步函數(shù)或链,如此一來惫恼,便產(chǎn)生了一個死循環(huán),導(dǎo)致死鎖

3. 在子線程中創(chuàng)建子線程
- (void)test3 {
    
    // 1. 獲取當(dāng)前線程(主線程)
    NSLog(@"currentThread --- %@", [NSThread currentThread]);

    // 2. 創(chuàng)建一個新的線程澳盐,并執(zhí)行指定方法
    [self performSelectorInBackground:@selector(runInSubthread:) withObject:@"在子線程的子線程中執(zhí)行任務(wù)"];

}

- (void)runInSubthread:(NSString *)str {

    // 1. 獲取當(dāng)前線程(子線程)
    NSLog(@"currentThread --- %@ --- %@", [NSThread currentThread], str);
    
    // 2. 獲取全局隊列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 3. 使用異步函數(shù)向全局隊列中添加任務(wù)(創(chuàng)建新的線程)
    dispatch_async(globalQueue, ^{
        
        NSLog(@"任務(wù) 1 --- %@", [NSThread currentThread]);
        
    });
    
    // 4. 使用同步函數(shù)向全局隊列中添加任務(wù)(在該線程中執(zhí)行)
    dispatch_sync(globalQueue, ^{
    
        NSLog(@"任務(wù) 2 --- %@", [NSThread currentThread]);
    
    });

}

運行結(jié)果


6.png

總結(jié) : 該程序共創(chuàng)建了 3 個線程祈纯,包括 : 主線程、performSelectorInBackground: withObject: 創(chuàng)建的線程叼耙,使用異步函數(shù)創(chuàng)建的線程

4. 加載圖片
#import "LHLoadImageViewViewController.h"

@interface LHLoadImageViewViewController ()

@property (nonatomic, strong) UIImageView * imageView;
@property (nonatomic, strong) UIImage * image;

@end

@implementation LHLoadImageViewViewController

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, 300, 600)];
    
    [self.view addSubview:_imageView];

}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    // 1. 獲取全局隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 2. 使用異步函數(shù)將任務(wù)添加到全局隊列中腕窥,即由子線程加載圖片
    dispatch_async(queue, ^{
        
        NSLog(@"currentThread --- %@", [NSThread currentThread]);
        
        // 1). 創(chuàng)建 URL
        NSURL * url = [NSURL URLWithString:@"http://pic.58pic.com/58pic/16/58/28/80M58PICTcs_1024.jpg"];
        
        // 2). 將 url 對應(yīng)的內(nèi)容轉(zhuǎn)換為 NSData 數(shù)據(jù)對象
        NSData * data = [NSData dataWithContentsOfURL:url];
        
        // 3). 用 NSData 數(shù)據(jù)對象的數(shù)據(jù)初始化 UIImage
        _image = [UIImage imageWithData:data];
        
        NSLog(@"加載圖片完成");
        
        // 4). 回到主線程刷新 UI
        dispatch_async(dispatch_get_main_queue(), ^{
            
            NSLog(@"currentThread --- %@", [NSThread currentThread]);
            
            _imageView.image = _image;
            
        });
        
    });

}
@end

運行結(jié)果


7.png

8.png

總結(jié) : 通常主線程用來刷新 UI 界面,而子線程用來做一些耗時的工作(加載圖片等)筛婉,從上述運行結(jié)果可以看出簇爆,加載圖片由子線程執(zhí)行,而刷新 UI 則有主線程執(zhí)行

5. 延時方法

前面說過的 sleepForTimeInterval: 方法 和 sleepUntilDate: 方法都是針對當(dāng)前已經(jīng)執(zhí)行線程的爽撒,而本節(jié)所說的延時方法是針對還未執(zhí)行的線程
1)使用 performSelector: withObject: afterDelay: 方法
使用該方法可以將指定的任務(wù)延遲多少時間(單位為秒)執(zhí)行入蛆,并且該方法在哪個線程中被調(diào)用,那么指定的任務(wù)也就在哪個線程中執(zhí)行

- (void)test1 {
    // 1. 獲取當(dāng)前線程(主線程)
    NSThread * mainThread = [NSThread currentThread];
    NSLog(@"currentThread -- %@", mainThread);
    // 2. 延時 2s 調(diào)用(在本線程中)
    [self performSelector:@selector(run:) withObject:@"延時2s" afterDelay:2.0];
}
- (void)run:(NSString *)arg {
    // 1. 獲取當(dāng)前線程
    NSThread * currentThread = [NSThread currentThread];
    NSLog(@"currentThread --- %@", currentThread);
}

運行結(jié)果


9.png

總結(jié) : 可以看出匆浙,因為調(diào)用 performSelector: withObject: afterDelay: 方法所在的線程為主線程安寺,所以 run: 方法也在主線程中執(zhí)行,并且延時了 2s
2)將 performSelector: withObject: afterDelay: 放在同步/異步函數(shù)中

- (void)test2 {
    NSLog(@"currentThread --- %@", [NSThread currentThread]);
    // 1. 獲取主隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 2. 使用異步函數(shù)向全局隊列添加任務(wù)
    dispatch_async(queue, ^{
        [self performSelector:@selector(run:) withObject:@"異步函數(shù)中執(zhí)行任務(wù)" afterDelay:4.0];
    });
    // 3. 使用同步函數(shù)向全局隊列添加任務(wù)
    dispatch_sync(queue, ^{
        [self performSelector:@selector(run:) withObject:@"同步函數(shù)中執(zhí)行任務(wù)" afterDelay:4.0];
    });
}
- (void)run:(NSString *)arg {
    // 1. 獲取當(dāng)前線程
    NSThread * currentThread = [NSThread currentThread];
    NSLog(@"currentThread --- %@ --- %@", currentThread, arg);
}

運行結(jié)果


10.png

總結(jié) : 可以看出首尼,只有同步函數(shù)執(zhí)行了任務(wù)挑庶,異步函數(shù)并沒有
可見言秸,將 performSelector: withObject: afterDelay: 方法 放在異步函數(shù)中是不起作用的
3)使用 dispatch_after 方法

// dispatch_after 函數(shù)
// 參數(shù) 1: 延時的時間
// 參數(shù) 2: 在哪個隊列中執(zhí)行
// 參數(shù) 3: 執(zhí)行任務(wù)的代碼塊
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);

dispatch_time_t 是表示時間類型,可以通過下面的函數(shù)創(chuàng)建

// dispatch_time 函數(shù)
// 參數(shù) 1: 從何時開始迎捺,一般用 DISPATCH_TIME_NOW 表示從當(dāng)前開始
// 參數(shù) 2: 延時的秒數(shù)举畸,單位為 納秒
// 便于對參數(shù) 2 的方便使用,有定義以下宏
/* 注意凳枝,這三個宏的單位都是納秒
#define NSEC_PER_SEC 1000000000ull   每秒有多少納秒
#define USEC_PER_SEC 1000000ull        每秒有多少毫秒
#define NSEC_PER_USEC 1000ull           每毫秒有多少納秒
*/
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);

延時 1s 將任務(wù)放到主隊列中的代碼如下

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
    
NSLog(@"延遲開始");
    
dispatch_after(time, dispatch_get_main_queue(), ^{
        
    NSLog(@"執(zhí)行任務(wù)中...");
        
    NSLog(@"延遲結(jié)束");
});

運行結(jié)果


11.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末抄沮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子岖瑰,更是在濱河造成了極大的恐慌叛买,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹋订,死亡現(xiàn)場離奇詭異率挣,居然都是意外死亡,警方通過查閱死者的電腦和手機露戒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門椒功,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人智什,你說我怎么就攤上這事动漾。” “怎么了荠锭?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵旱眯,是天一觀的道長。 經(jīng)常有香客問我节沦,道長键思,這世上最難降的妖魔是什么础爬? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任甫贯,我火速辦了婚禮,結(jié)果婚禮上看蚜,老公的妹妹穿的比我還像新娘叫搁。我一直安慰自己,他們只是感情好供炎,可當(dāng)我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布渴逻。 她就那樣靜靜地躺著,像睡著了一般音诫。 火紅的嫁衣襯著肌膚如雪惨奕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天竭钝,我揣著相機與錄音梨撞,去河邊找鬼雹洗。 笑死,一個胖子當(dāng)著我的面吹牛卧波,可吹牛的內(nèi)容都是我干的时肿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼港粱,長吁一口氣:“原來是場噩夢啊……” “哼螃成!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起查坪,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤寸宏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后偿曙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體击吱,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年遥昧,在試婚紗的時候發(fā)現(xiàn)自己被綠了覆醇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡炭臭,死狀恐怖永脓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鞋仍,我是刑警寧澤常摧,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站威创,受9級特大地震影響落午,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肚豺,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一溃斋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吸申,春花似錦梗劫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至日丹,卻和暖如春走哺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哲虾。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工丙躏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留齐帚,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓彼哼,卻偏偏與公主長得像对妄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子敢朱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,527評論 2 349

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