iOS開發(fā)中,多線程是一個重要的知識點(diǎn),在進(jìn)行下面內(nèi)容之前,有必要先講一下進(jìn)程和線程的概念。
進(jìn)程:系統(tǒng)中正在運(yùn)行的一個應(yīng)用程序,每個進(jìn)程之間都是獨(dú)立的,每個進(jìn)程都運(yùn)行在其專有的空間之內(nèi),并且這個空間是受保護(hù)的,也就是一個進(jìn)程是不能去訪問另一個進(jìn)程的獨(dú)有空間的。進(jìn)程例如:XCode,Safari,QQ等運(yùn)行的時候都是一個單獨(dú)的進(jìn)程。
進(jìn)程的五態(tài)模型:新建、就緒、運(yùn)行、阻塞、終止。
線程:線程是進(jìn)程的基本執(zhí)行單元,進(jìn)程的所有任務(wù)都在線程中執(zhí)行,換句話說一個進(jìn)程想要執(zhí)行任務(wù)必須至少有一個線程,也就是我們常說的主線程,在iOS開發(fā)中我們也稱之為UI線程。一個進(jìn)程可以同時開啟幾條線程,而一個線程只能同時在一個進(jìn)程內(nèi)。
多線程的使用場景:
——網(wǎng)絡(luò)請求
——文件處理
——圖片加載
——數(shù)據(jù)存儲
——任務(wù)執(zhí)行
任務(wù)執(zhí)行的方式:
串行:多個任務(wù)按照一定順序執(zhí)行。
并行:多個任務(wù)同時執(zhí)行。
多線程的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):1、簡化了編程模型 缺點(diǎn):1、增加程序設(shè)計復(fù)雜性
2、更加的輕量級 2、占用內(nèi)存空間
3、提高了執(zhí)行效率 3、增大cpu調(diào)度開銷
4、提高了資源的利用率
iOS開發(fā)多線程的實(shí)現(xiàn)方案:
pThread、NSThread、GCD、NSOperation
一、多線程之pThread
pThread是很多操作系統(tǒng)上都用到的多線程的API,移植性特別強(qiáng),在iOS平臺上也是可以的,不過它是基于C語言框架的,在iOS開發(fā)過程中用的很少。
廢話少說上代碼:
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 100, 50)];
[btn setTitle:@"pThread" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[btn addTarget:self action:@selector(clickPthread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
- (void)clickPthread{
NSLog(@"我在線程%@中執(zhí)行",[NSThread currentThread]);
/*
參數(shù)一:表示傳入線程對象的地址,不能為空
參數(shù)二:表示線程的一些屬性,可以為空
參數(shù)三:表示指向某個函數(shù)的指針,這個不能為空
參數(shù)四:表示函數(shù)需要接收的參數(shù),也可以為空
*/
pthread_t pthread;
pthread_create(&pthread, NULL, run, NULL);
}
void *run(void *data){
NSLog(@"我在%@線程中執(zhí)行",[NSThread currentThread]);
for (int i = 0; i < 4; i ++) {
NSLog(@"%d",i);
sleep(1);
}
return NULL;
}
控制臺打印結(jié)果:
// 2547進(jìn)程的唯一標(biāo)示碼,178761和179079是線程的唯一標(biāo)示碼
2017-02-23 15:16:55.770 pThreadTest[2547:178761] 我在線程<NSThread: 0x600000065a40>{number = 1, name = main}中執(zhí)行
2017-02-23 15:16:55.770 pThreadTest[2547:179079] 我在<NSThread: 0x608000068a00>{number = 3, name = (null)}線程中執(zhí)行
2017-02-23 15:16:55.770 pThreadTest[2547:179079] 0
2017-02-23 15:16:56.771 pThreadTest[2547:179079] 1
2017-02-23 15:16:57.777 pThreadTest[2547:179079] 2
2017-02-23 15:16:58.779 pThreadTest[2547:179079] 3
二、多線程之NSThread
面向?qū)ο蟮?,我們可以直接來操控線程的對象。
廢話少說上代碼:
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 150, 100, 50)];
[btn setTitle:@"NSThread" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[btn addTarget:self action:@selector(clickNSThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
- (void)clickNSThread{
NSLog(@"我在%@線程中執(zhí)行",[NSThread currentThread]);
// //1.通過alloc init的方式創(chuàng)建并執(zhí)行線程
// NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(runThread1) object:nil];
// [thread1 start];
//
// //2.通過detachNewThreadSelector 方式創(chuàng)建并執(zhí)行線程
// [NSThread detachNewThreadSelector:@selector(runThread1) toTarget:self withObject:nil];
//3.通過performSelectorInBackground 方式創(chuàng)建線程
[self performSelectorInBackground:@selector(runThread1) withObject:nil];
}
- (void)runThread1{
NSLog(@"我在%@線程中執(zhí)行",[NSThread currentThread]);
for (int i = 0; i < 4; i ++) {
NSLog(@"%d",i);
sleep(1);
if (i == 3) {
//回到主線程執(zhí)行
[self performSelectorOnMainThread:@selector(runMainThread) withObject:nil waitUntilDone:YES];
}
}
}
- (void)runMainThread{
NSLog(@"我在%@線程中執(zhí)行",[NSThread currentThread]);
}
控制臺打印結(jié)果:
2017-02-23 15:48:49.561 pThreadTest[2642:199011] 我在<NSThread: 0x60000007f9c0>{number = 1, name = main}線程中執(zhí)行
2017-02-23 15:48:49.562 pThreadTest[2642:199229] 我在<NSThread: 0x600000270cc0>{number = 3, name = (null)}線程中執(zhí)行
2017-02-23 15:48:49.562 pThreadTest[2642:199229] 0
2017-02-23 15:48:50.628 pThreadTest[2642:199229] 1
2017-02-23 15:48:51.702 pThreadTest[2642:199229] 2
2017-02-23 15:48:52.776 pThreadTest[2642:199229] 3
2017-02-23 15:48:53.852 pThreadTest[2642:199011] 我在<NSThread: 0x60000007f9c0>{number = 1, name = main}線程中執(zhí)行
設(shè)置thread的屬性:
//1.通過alloc init的方式創(chuàng)建并執(zhí)行線程(用的比較多,因為可以拿到線程對象,設(shè)置他的屬性)
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(runThread1) object:nil];
//設(shè)置thread的name
[thread1 setName:@"name_thread1"];
//設(shè)置thread的優(yōu)先級
[thread1 setThreadPriority:0.2];
[thread1 start];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(runThread1) object:nil];
[thread2 setName:@"name_thread2"];
[thread2 setThreadPriority:0.5];
[thread2 start];
控制臺打印結(jié)果:
2017-02-23 16:05:12.834 pThreadTest[2722:210010] 我在<NSThread: 0x60000007ee80>{number = 4, name = name_thread2}線程中執(zhí)行
2017-02-23 16:05:12.834 pThreadTest[2722:210010] 0
2017-02-23 16:05:12.834 pThreadTest[2722:210009] 我在<NSThread: 0x60000007f0c0>{number = 3, name = name_thread1}線程中執(zhí)行
2017-02-23 16:05:12.835 pThreadTest[2722:210009] 0
2017-02-23 16:05:13.835 pThreadTest[2722:210010] 1
2017-02-23 16:05:13.835 pThreadTest[2722:210009] 1
2017-02-23 16:05:14.837 pThreadTest[2722:210010] 2
2017-02-23 16:05:14.837 pThreadTest[2722:210009] 2
2017-02-23 16:05:15.837 pThreadTest[2722:210010] 3
2017-02-23 16:05:15.838 pThreadTest[2722:210009] 3
從控制臺的打印結(jié)果可以看到thread1的name和thread2的name;同時也能發(fā)現(xiàn)thread2執(zhí)行的每一步要優(yōu)先thread1執(zhí)行。但是這個優(yōu)先執(zhí)行跟我們所說的串行不同,不是說thread2完全執(zhí)行完畢再執(zhí)行thread1,而是cpu在分配資源的時候優(yōu)先分配給thread2,使其優(yōu)先執(zhí)行。
NSThread鎖的使用:
直接上代碼:
//
// TicketManager.h
// pThreadTest
//
// Created by Dustin on 17/2/23.
// Copyright ? 2017年 Dustin. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface TicketManager : NSObject
- (void)startToSale;
@end
//
// TicketManager.m
// pThreadTest
//
// Created by Dustin on 17/2/23.
// Copyright ? 2017年 Dustin. All rights reserved.
//
#import "TicketManager.h"
#define Total 50
@interface TicketManager()
@property (nonatomic,assign) int tickets;//剩余票數(shù)
@property (nonatomic,assign) int saleCount;//賣出票數(shù)
@property (nonatomic,strong) NSThread *threadBJ;
@property (nonatomic,strong) NSThread *threadSH;
@property (nonatomic,strong) NSCondition *ticketCondition;
@end
@implementation TicketManager
- (instancetype)init{
if (self = [super init]) {
self.ticketCondition = [[NSCondition alloc] init];
self.tickets = Total;
self.threadBJ = [[NSThread alloc] initWithTarget:self selector:@selector(sale) object:nil];
[self.threadBJ setName:@"BJ_thread"];
self.threadSH = [[NSThread alloc] initWithTarget:self selector:@selector(sale) object:nil];
[self.threadSH setName:@"SH_thread"];
}
return self;
}
- (void)sale{
while (true) {
//需要加鎖,不然會發(fā)生資源搶占
[self.ticketCondition lock];
//下面的加鎖方式也可以
// @synchronized (self) {
//tickets>0 說明還有票可以賣
if (self.tickets > 0) {
[NSThread sleepForTimeInterval:0.5];
self.tickets --;
self.saleCount = Total - self.tickets;
NSLog(@"%@: 當(dāng)前余票: %d,售出: %d",[NSThread currentThread].name,self.tickets,self.saleCount);
}
// }
[self.ticketCondition unlock];
}
}
- (void)startToSale{
[self.threadBJ start];
[self.threadSH start];
}
@end
//控制器里面調(diào)用
//
// ViewController.m
// pThreadTest
//
// Created by Dustin on 17/2/23.
// Copyright ? 2017年 Dustin. All rights reserved.
//
#import "ViewController.h"
#import <pthread.h>
#import "TicketManager.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
TicketManager *manager = [[TicketManager alloc] init];
[manager startToSale];
}
三、多線程之GCD
GCD是蘋果為了多核并行運(yùn)算提出的一套解決方案。他可以合理的利用更多的CPU內(nèi)核,并且能夠自動管理線程的生命周期,比如創(chuàng)建線程,任務(wù)調(diào)度,銷毀線程等都不需要我們手動管理,你只需要告訴GCD干什么就行了。
同步&異步:區(qū)別會不會阻塞當(dāng)前線程,同步會阻塞當(dāng)前線程,而異步則不會。
最基本用法代碼:
- (void)viewDidLoad {
UIButton *btn2 = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 50)];
[btn2 setTitle:@"GCD" forState:UIControlStateNormal];
[btn2 setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[btn2 addTarget:self action:@selector(clickGCD) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn2];
}
- (void)clickGCD{
NSLog(@"執(zhí)行GCD在線程%@",[NSThread currentThread]);
/*
dispatch_get_global_queue 全局并發(fā)隊列
參數(shù)1:設(shè)置優(yōu)先級,優(yōu)先級越高越優(yōu)先執(zhí)行
#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
*/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//執(zhí)行耗時操作
NSLog(@"start task1 在線程%@",[NSThread currentThread]);
//dispatch_get_main_queue()主線程隊列
dispatch_async(dispatch_get_main_queue(), ^{
//回到主線程刷新UI
NSLog(@"刷新UI在線程%@",[NSThread currentThread]);
});
});
}
控制臺打印結(jié)果:
2017-02-23 16:55:33.964 pThreadTest[2851:246460] 執(zhí)行GCD在線程<NSThread: 0x6000000656c0>{number = 1, name = main}
2017-02-23 16:55:33.965 pThreadTest[2851:246949] start task1 在線程<NSThread: 0x60800006c9c0>{number = 3, name = (null)}
2017-02-23 16:55:33.965 pThreadTest[2851:246460] 刷新UI在線程<NSThread: 0x6000000656c0>{number = 1, name = main}
GCD的串行隊列:
代碼:
/*
創(chuàng)建隊列
參數(shù)一:char類型的
參數(shù)二:選擇串行還是并行
DISPATCH_QUEUE_SERIAL串行
DISPATCH_QUEUE_CONCURRENT并行
*/
dispatch_queue_t queue = dispatch_queue_create("com.GCD.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"start task 1");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 1");
});
dispatch_async(queue, ^{
NSLog(@"start task 2");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 2");
});
dispatch_async(queue, ^{
NSLog(@"start task 3");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 3");
});
控制臺打印結(jié)果:
2017-02-23 17:17:19.370 pThreadTest[2910:261786] start task 1
2017-02-23 17:17:21.376 pThreadTest[2910:261786] end task 1
2017-02-23 17:17:21.376 pThreadTest[2910:261786] start task 2
2017-02-23 17:17:23.379 pThreadTest[2910:261786] end task 2
2017-02-23 17:17:23.379 pThreadTest[2910:261786] start task 3
2017-02-23 17:17:25.383 pThreadTest[2910:261786] end task 3
//由此看出三個任務(wù)是依次執(zhí)行的,并且線程標(biāo)識碼261786是一致的,說明為了保證依次執(zhí)行,串行隊列是在同一個線程執(zhí)行的任務(wù)。
GCD的并行隊列:
代碼:
/*
創(chuàng)建隊列
參數(shù)一:char類型的
參數(shù)二:選擇串行還是并行
DISPATCH_QUEUE_SERIAL串行
DISPATCH_QUEUE_CONCURRENT并行
*/
dispatch_queue_t queue = dispatch_queue_create("com.GCD.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"start task 1");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 1");
});
dispatch_async(queue, ^{
NSLog(@"start task 2");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 2");
});
dispatch_async(queue, ^{
NSLog(@"start task 3");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 3");
});
控制臺打印結(jié)果:
2017-02-23 17:20:19.570 pThreadTest[2925:264132] start task 2
2017-02-23 17:20:19.570 pThreadTest[2925:264134] start task 1
2017-02-23 17:20:19.570 pThreadTest[2925:264131] start task 3
2017-02-23 17:20:21.573 pThreadTest[2925:264134] end task 1
2017-02-23 17:20:21.573 pThreadTest[2925:264132] end task 2
2017-02-23 17:20:21.573 pThreadTest[2925:264131] end task 3
//由此看出三個任務(wù)是并發(fā)執(zhí)行的,并且線程有三個不同的標(biāo)識碼,說明為了保證并發(fā)執(zhí)行,并行隊列開啟了三個不同的線程。
dispatch_group_t:在開發(fā)中我們經(jīng)常有這樣的需求,在多個任務(wù)異步處理之后,我們需要一個統(tǒng)一的回調(diào)通知,來告訴我們所有的任務(wù)都已結(jié)束,然后我們根據(jù)也無需求再去處理其他的業(yè)務(wù)。這個時候就要用到dispatch_group_t。
上代碼:
NSLog(@"我是主線程");
dispatch_queue_t queue = dispatch_queue_create("com.GCD.group", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"start task 1");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"start task 2");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"start task 3");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 3");
});
//所有任務(wù)完成之后group的回調(diào)
dispatch_group_notify(group, queue, ^{
NSLog(@"All tasks over");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主線程刷新UI");
});
});
控制臺打印結(jié)果:
2017-02-23 17:38:06.462 pThreadTest[2984:277621] 我是主線程
2017-02-23 17:38:06.462 pThreadTest[2984:278062] start task 1
2017-02-23 17:38:06.462 pThreadTest[2984:278066] start task 2
2017-02-23 17:38:06.462 pThreadTest[2984:278067] start task 3
2017-02-23 17:38:08.464 pThreadTest[2984:278062] end task 1
2017-02-23 17:38:08.464 pThreadTest[2984:278067] end task 3
2017-02-23 17:38:08.464 pThreadTest[2984:278066] end task 2
2017-02-23 17:38:08.465 pThreadTest[2984:278066] All tasks over
2017-02-23 17:38:08.465 pThreadTest[2984:277621] 回到主線程刷新UI
//由打印結(jié)果可以看出,dispatch_group_t在所有任務(wù)結(jié)束之后回調(diào)的通知的線程標(biāo)識碼(278066)和主線程的標(biāo)識碼(277621)不同,可以確定回調(diào)的通知是在子線程執(zhí)行的,并且,系統(tǒng)并沒有新開一條子線程,而是在原來執(zhí)行任務(wù)的子線程選擇了一條,因此,如果接收到回調(diào)通知后,需要刷新UI還需要回到主線程操作。
在實(shí)際的開發(fā)工程中我們可能有這樣的需求,有幾個異步的請求需要執(zhí)行,執(zhí)行完了統(tǒng)一告訴我們?nèi)缓髨?zhí)行UI操作,于是寫如下的代碼:
- (void)clickGCD{
NSLog(@"我是主線程");
dispatch_queue_t queue = dispatch_queue_create("com.GCD.group", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
[self sendRequest1:^{
NSLog(@"request1 done");
}];
});
dispatch_group_async(group, queue, ^{
[self sendRequest2:^{
NSLog(@"request2 done");
}];
});
//所有任務(wù)完成之后group的回調(diào)
dispatch_group_notify(group, queue, ^{
NSLog(@"All tasks over");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主線程刷新UI");
});
});
}
//網(wǎng)絡(luò)請求1
- (void)sendRequest1:(void(^)())block{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task 1");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 1");
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block();
}
});
});
}
//網(wǎng)絡(luò)請求2
- (void)sendRequest2:(void(^)())block{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task 2");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 2");
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block();
}
});
});
}
控制臺打印結(jié)果:
2017-02-23 18:04:07.967 pThreadTest[3082:293445] 我是主線程
2017-02-23 18:04:07.967 pThreadTest[3082:293706] start task 1
2017-02-23 18:04:07.967 pThreadTest[3082:293704] start task 2
2017-02-23 18:04:07.967 pThreadTest[3082:293707] All tasks over
2017-02-23 18:04:07.968 pThreadTest[3082:293445] 回到主線程刷新UI
2017-02-23 18:04:09.972 pThreadTest[3082:293706] end task 1
2017-02-23 18:04:09.972 pThreadTest[3082:293704] end task 2
2017-02-23 18:04:09.972 pThreadTest[3082:293445] request1 done
2017-02-23 18:04:09.972 pThreadTest[3082:293445] request2 done
//很奇怪,網(wǎng)絡(luò)請求還沒有執(zhí)行完,group完成的回調(diào)通知已經(jīng)結(jié)束了,這是因為加入group的操作是異步請求,兩個任務(wù)瞬間走過了,而異步的請求還在執(zhí)行。
為了解決上面的問題dispatch_group_t為我們提供了下面enter和leave的方法:
代碼:
- (void)clickGCD{
NSLog(@"我是主線程");
dispatch_queue_t queue = dispatch_queue_create("com.GCD.group", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self sendRequest1:^{
NSLog(@"request1 done");
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self sendRequest2:^{
NSLog(@"request2 done");
dispatch_group_leave(group);
}];
//所有任務(wù)完成之后group的回調(diào)
dispatch_group_notify(group, queue, ^{
NSLog(@"All tasks over");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主線程刷新UI");
});
});
}
//網(wǎng)絡(luò)請求1
- (void)sendRequest1:(void(^)())block{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task 1");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 1");
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block();
}
});
});
}
//網(wǎng)絡(luò)請求2
- (void)sendRequest2:(void(^)())block{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"start task 2");
[NSThread sleepForTimeInterval:2];
NSLog(@"end task 2");
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block();
}
});
});
}
控制臺打印的結(jié)果:
2017-02-23 18:09:43.277 pThreadTest[3115:297830] 我是主線程
2017-02-23 18:09:43.277 pThreadTest[3115:298153] start task 1
2017-02-23 18:09:43.277 pThreadTest[3115:298155] start task 2
2017-02-23 18:09:45.283 pThreadTest[3115:298155] end task 2
2017-02-23 18:09:45.283 pThreadTest[3115:298153] end task 1
2017-02-23 18:09:45.283 pThreadTest[3115:297830] request2 done
2017-02-23 18:09:45.283 pThreadTest[3115:297830] request1 done
2017-02-23 18:09:45.284 pThreadTest[3115:298153] All tasks over
2017-02-23 18:09:45.284 pThreadTest[3115:297830] 回到主線程刷新UI
GCD模式單例:
上代碼:
//
// testSingle.h
// pThreadTest
//
// Created by Dustin on 17/2/23.
// Copyright ? 2017年 Dustin. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface testSingle : NSObject
+ (instancetype)shareInstance;
@end
//
// testSingle.m
// pThreadTest
//
// Created by Dustin on 17/2/23.
// Copyright ? 2017年 Dustin. All rights reserved.
//
#import "testSingle.h"
static id _instance=nil;
@implementation testSingle
+ (instancetype)shareInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[testSingle alloc] init];
NSLog(@"init the testSingle");
});
return _instance;
}
@end
//控制器中的調(diào)用
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(100, 250, 100, 50)];
[btn setTitle:@"single" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[btn addTarget:self action:@selector(clickSingle) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
//點(diǎn)擊按鈕不停的調(diào)用
- (void)clickSingle{
testSingle *single = [testSingle shareInstance];
NSLog(@"%@",single);
}
控制臺打印結(jié)果:
2017-02-23 18:38:05.637 pThreadTest[3368:314898] init the testSingle
2017-02-23 18:38:05.637 pThreadTest[3368:314898] <testSingle: 0x600000000d60>
2017-02-23 18:38:06.437 pThreadTest[3368:314898] <testSingle: 0x600000000d60>
2017-02-23 18:38:07.013 pThreadTest[3368:314898] <testSingle: 0x600000000d60>
2017-02-23 18:38:07.317 pThreadTest[3368:314898] <testSingle: 0x600000000d60>
2017-02-23 18:38:07.509 pThreadTest[3368:314898] <testSingle: 0x600000000d60>
//由打印結(jié)果看出testSingle只被初始化了一次,并且只存在一個實(shí)例。
如果我們想使某一段代碼在應(yīng)用程序的整個生命周期只執(zhí)行一次的話,我們也可以用GCD來限制:
代碼:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"excute only once");
});
GCD之延遲執(zhí)行:
缺點(diǎn):無法取消。
- (void)clickDelayExecute{
NSLog(@"--begin--");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"delay execute");
});
}
控制臺打印結(jié)果:
2017-02-23 18:55:03.120 pThreadTest[3436:326559] --begin--
2017-02-23 18:55:05.299 pThreadTest[3436:326559] delay execute
//開始時間:18:55:03.120 執(zhí)行時間:18:55:05.299
四、多線程之NSOperation
NSOperation其實(shí)是對GCD的封裝,它的實(shí)例封裝了需要執(zhí)行的操作,以及執(zhí)行操作所需要的數(shù)據(jù),并且可以以并發(fā)或者非并發(fā)的形式執(zhí)行這個操作。
兩種使用方式:
1、NSInvovationOperation&NSBlockOperation
2、自定義類繼承NSOperation
相關(guān)概念:
1、NSOperationQueue 隊列,線程池
a、addOperation 將線程加入線程池
b、setMaxConcurrentOperationCount 設(shè)置線程池同一時間最大的線程數(shù)
2、狀態(tài)
ready、cancelled、executing、finished、asynchronous(并發(fā)還是非并發(fā))
3、依賴-addDependency
廢話少說,上代碼:
//NSInvocationOperation
- (void)NSOperationTest{
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction) object:nil];
[operation start];
}
- (void)invocationAction{
NSLog(@"main thread%@",[NSThread currentThread]);
for (int i = 0; i <=3; i ++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"%d 在線程%@",i,[NSThread currentThread]);
}
}
控制臺打?。?/p>
2017-02-23 19:17:00.487 pThreadTest[3498:342096] main thread<NSThread: 0x60000007ee80>{number = 1, name = main}
2017-02-23 19:17:01.489 pThreadTest[3498:342096] 0 在線程<NSThread: 0x60000007ee80>{number = 1, name = main}
2017-02-23 19:17:02.490 pThreadTest[3498:342096] 1 在線程<NSThread: 0x60000007ee80>{number = 1, name = main}
2017-02-23 19:17:03.491 pThreadTest[3498:342096] 2 在線程<NSThread: 0x60000007ee80>{number = 1, name = main}
2017-02-23 19:17:04.493 pThreadTest[3498:342096] 3 在線程<NSThread: 0x60000007ee80>{number = 1, name = main}
//說明NSInvocationOperation操作是在主線程執(zhí)行
- (void)NSOperationTest{
// NSBlockOperation
NSLog(@"main thread%@",[NSThread currentThread]);
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i <=3; i ++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"%d 在線程%@",i,[NSThread currentThread]);
}
}];
[blockOperation start];
}
控制臺打?。?/p>
2017-02-23 19:25:09.868 pThreadTest[3524:346135] main thread<NSThread: 0x608000071800>{number = 1, name = main}
2017-02-23 19:25:10.870 pThreadTest[3524:346135] 0 在線程<NSThread: 0x608000071800>{number = 1, name = main}
2017-02-23 19:25:11.871 pThreadTest[3524:346135] 1 在線程<NSThread: 0x608000071800>{number = 1, name = main}
2017-02-23 19:25:12.872 pThreadTest[3524:346135] 2 在線程<NSThread: 0x608000071800>{number = 1, name = main}
2017-02-23 19:25:13.874 pThreadTest[3524:346135] 3 在線程<NSThread: 0x608000071800>{number = 1, name = main}
//說明NSBlockOperation操作是在主線程執(zhí)行
上面兩種方式都是在主線程創(chuàng)建操作,那么NSOperation怎么用異步線程呢?這里就用到了NSOperationQueue:
上代碼:
@property (nonatomic,strong) NSOperationQueue *queue;
- (void)NSOperationTest{
NSLog(@"main thread%@",[NSThread currentThread]);
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i <=3; i ++) {
[NSThread sleepForTimeInterval:1];
NSLog(@"%d 在線程%@",i,[NSThread currentThread]);
}
}];
if (!self.queue) {
self.queue = [[NSOperationQueue alloc] init];
}
[self.queue addOperation:blockOperation];
NSLog(@"end");
}
控制臺打印:
2017-02-23 19:30:25.969 pThreadTest[3543:350315] main thread<NSThread: 0x608000072600>{number = 1, name = main}
2017-02-23 19:30:25.970 pThreadTest[3543:350315] end
2017-02-23 19:30:27.042 pThreadTest[3543:350428] 0 在線程<NSThread: 0x60800007b140>{number = 3, name = (null)}
2017-02-23 19:30:28.114 pThreadTest[3543:350428] 1 在線程<NSThread: 0x60800007b140>{number = 3, name = (null)}
2017-02-23 19:30:29.183 pThreadTest[3543:350428] 2 在線程<NSThread: 0x60800007b140>{number = 3, name = (null)}
2017-02-23 19:30:30.228 pThreadTest[3543:350428] 3 在線程<NSThread: 0x60800007b140>{number = 3, name = (null)}
//end先執(zhí)行,說明是異步執(zhí)行,線程的編碼不同,說明是在自線程中執(zhí)行。
NSOperation自定義:
上代碼:
//
// customOperation.h
// pThreadTest
//
// Created by Dustin on 17/2/23.
// Copyright ? 2017年 Dustin. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface customOperation : NSOperation
- (instancetype)initWithName:(NSString *)name;
@end
//
// customOperation.m
// pThreadTest
//
// Created by Dustin on 17/2/23.
// Copyright ? 2017年 Dustin. All rights reserved.
//
#import "customOperation.h"
@interface customOperation()
@property (nonatomic,copy)NSString *operName;
@end
@implementation customOperation
- (instancetype)initWithName:(NSString *)name{
if (self = [super init]) {
self.operName = name;
}
return self;
}
//重寫main方法
- (void)main{
for (int i = 0; i < 3; i ++) {
NSLog(@"%@ %d",self.operName,i);
[NSThread sleepForTimeInterval:1];
}
}
@end
//
// ViewController.m
- (void)NSOperationTest{
NSLog(@"main thread%@",[NSThread currentThread]);
if (!self.queue) {
self.queue = [[NSOperationQueue alloc] init];
}
customOperation *customA = [[customOperation alloc] initWithName:@"customA"];
[self.queue addOperation:customA];
NSLog(@"end");
}
控制臺打印結(jié)果:
2017-02-23 19:51:03.284 pThreadTest[3621:365709] main thread<NSThread: 0x600000263a80>{number = 1, name = main}
2017-02-23 19:51:03.285 pThreadTest[3621:365709] end
2017-02-23 19:51:03.285 pThreadTest[3621:365846] customA 0
2017-02-23 19:51:04.359 pThreadTest[3621:365846] customA 1
2017-02-23 19:51:05.433 pThreadTest[3621:365846] customA 2
//由打印結(jié)果可以看出操作,是異步子線程中執(zhí)行
設(shè)置線程池同時最大線程數(shù):
上代碼:
//
// customOperation.h
// pThreadTest
//
// Created by Dustin on 17/2/23.
// Copyright ? 2017年 Dustin. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface customOperation : NSOperation
- (instancetype)initWithName:(NSString *)name;
@end
//
// customOperation.m
// pThreadTest
//
// Created by Dustin on 17/2/23.
// Copyright ? 2017年 Dustin. All rights reserved.
//
#import "customOperation.h"
@interface customOperation()
@property (nonatomic,copy)NSString *operName;
@end
@implementation customOperation
- (instancetype)initWithName:(NSString *)name{
if (self = [super init]) {
self.operName = name;
}
return self;
}
//重寫main方法
- (void)main{
for (int i = 0; i < 3; i ++) {
NSLog(@"%@ %d",self.operName,i);
[NSThread sleepForTimeInterval:1];
}
}
@end
//
// ViewController.m
- (void)NSOperationTest{
NSLog(@"main thread%@",[NSThread currentThread]);
if (!self.queue) {
self.queue = [[NSOperationQueue alloc] init];
}
//設(shè)置線程池同時最大線程數(shù)
[self.queue setMaxConcurrentOperationCount:1];
customOperation *customA = [[customOperation alloc] initWithName:@"customA"];
customOperation *customB = [[customOperation alloc] initWithName:@"customB"];
customOperation *customC = [[customOperation alloc] initWithName:@"customC"];
customOperation *customD = [[customOperation alloc] initWithName:@"customD"];
[self.queue addOperation:customA];
[self.queue addOperation:customB];
[self.queue addOperation:customC];
[self.queue addOperation:customD];
NSLog(@"end");
}
控制臺打印結(jié)果:
當(dāng)[self.queue setMaxConcurrentOperationCount:1];時
2017-02-23 19:54:44.725 pThreadTest[3647:367833] main thread<NSThread: 0x600000076e80>{number = 1, name = main}
2017-02-23 19:54:44.726 pThreadTest[3647:367833] end
2017-02-23 19:54:44.726 pThreadTest[3647:368602] customA 0
2017-02-23 19:54:45.800 pThreadTest[3647:368602] customA 1
2017-02-23 19:54:46.809 pThreadTest[3647:368602] customA 2
2017-02-23 19:54:47.882 pThreadTest[3647:368610] customB 0
2017-02-23 19:54:48.957 pThreadTest[3647:368610] customB 1
2017-02-23 19:54:50.028 pThreadTest[3647:368610] customB 2
2017-02-23 19:54:51.098 pThreadTest[3647:368602] customC 0
2017-02-23 19:54:52.172 pThreadTest[3647:368602] customC 1
2017-02-23 19:54:53.241 pThreadTest[3647:368602] customC 2
2017-02-23 19:54:54.308 pThreadTest[3647:368610] customD 0
2017-02-23 19:54:55.379 pThreadTest[3647:368610] customD 1
2017-02-23 19:54:56.451 pThreadTest[3647:368610] customD 2
//當(dāng)最大線程數(shù)1時依次執(zhí)行
當(dāng)[self.queue setMaxConcurrentOperationCount:3];時
2017-02-23 19:56:05.337 pThreadTest[3662:369743] main thread<NSThread: 0x600000070740>{number = 1, name = main}
2017-02-23 19:56:05.337 pThreadTest[3662:369743] end
2017-02-23 19:56:05.338 pThreadTest[3662:369819] customB 0
2017-02-23 19:56:05.338 pThreadTest[3662:369789] customA 0
2017-02-23 19:56:05.338 pThreadTest[3662:369792] customC 0
2017-02-23 19:56:06.413 pThreadTest[3662:369792] customC 1
2017-02-23 19:56:06.413 pThreadTest[3662:369819] customB 1
2017-02-23 19:56:06.413 pThreadTest[3662:369789] customA 1
2017-02-23 19:56:07.485 pThreadTest[3662:369792] customC 2
2017-02-23 19:56:07.485 pThreadTest[3662:369819] customB 2
2017-02-23 19:56:07.485 pThreadTest[3662:369789] customA 2
2017-02-23 19:56:08.561 pThreadTest[3662:369790] customD 0
2017-02-23 19:56:09.635 pThreadTest[3662:369790] customD 1
2017-02-23 19:56:10.709 pThreadTest[3662:369790] customD 2
//當(dāng)最大線程數(shù)3的時候就并行執(zhí)行
設(shè)置NSOperation之間的互相依賴
上代碼:
//
// ViewController.m
- (void)NSOperationTest{
NSLog(@"main thread%@",[NSThread currentThread]);
if (!self.queue) {
self.queue = [[NSOperationQueue alloc] init];
}
//設(shè)置線程池同時最大線程數(shù)
[self.queue setMaxConcurrentOperationCount:3];
customOperation *customA = [[customOperation alloc] initWithName:@"customA"];
customOperation *customB = [[customOperation alloc] initWithName:@"customB"];
customOperation *customC = [[customOperation alloc] initWithName:@"customC"];
customOperation *customD = [[customOperation alloc] initWithName:@"customD"];
//給操作之間添加依賴
[customD addDependency:customA];
[customA addDependency:customC];
[customC addDependency:customB];
//下面這句不能加不然會死鎖
// [customB addDependency:customD];
[self.queue addOperation:customA];
[self.queue addOperation:customB];
[self.queue addOperation:customC];
[self.queue addOperation:customD];
NSLog(@"end");
}
控制臺打印結(jié)果:
2017-02-23 20:01:51.687 pThreadTest[3701:374279] main thread<NSThread: 0x600000066180>{number = 1, name = main}
2017-02-23 20:01:51.688 pThreadTest[3701:374279] end
2017-02-23 20:01:51.688 pThreadTest[3701:374318] customB 0
2017-02-23 20:01:52.740 pThreadTest[3701:374318] customB 1
2017-02-23 20:01:53.808 pThreadTest[3701:374318] customB 2
2017-02-23 20:01:54.884 pThreadTest[3701:374321] customC 0
2017-02-23 20:01:55.951 pThreadTest[3701:374321] customC 1
2017-02-23 20:01:57.025 pThreadTest[3701:374321] customC 2
2017-02-23 20:01:58.026 pThreadTest[3701:374321] customA 0
2017-02-23 20:01:59.100 pThreadTest[3701:374321] customA 1
2017-02-23 20:02:00.101 pThreadTest[3701:374321] customA 2
2017-02-23 20:02:01.172 pThreadTest[3701:374318] customD 0
2017-02-23 20:02:02.230 pThreadTest[3701:374318] customD 1
2017-02-23 20:02:03.231 pThreadTest[3701:374318] customD 2
當(dāng)自定義的NSOperation添加依賴,并且operation中添加異步操作時:
上代碼:
//
// customOperation.h
// pThreadTest
//
// Created by Dustin on 17/2/23.
// Copyright ? 2017年 Dustin. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface customOperation : NSOperation
- (instancetype)initWithName:(NSString *)name;
@end
//
// customOperation.m
// pThreadTest
//
// Created by Dustin on 17/2/23.
// Copyright ? 2017年 Dustin. All rights reserved.
//
#import "customOperation.h"
@interface customOperation()
@property (nonatomic,copy)NSString *operName;
@end
@implementation customOperation
- (instancetype)initWithName:(NSString *)name{
if (self = [super init]) {
self.operName = name;
}
return self;
}
//重寫main方法
- (void)main{
// for (int i = 0; i < 3; i ++) {
// NSLog(@"%@ %d",self.operName,i);
// [NSThread sleepForTimeInterval:1];
// }
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:1];
if (self.cancelled) {
return ;
}
NSLog(@"%@",self.operName);
});
}
//
// ViewController.m
- (void)NSOperationTest{
NSLog(@"main thread%@",[NSThread currentThread]);
if (!self.queue) {
self.queue = [[NSOperationQueue alloc] init];
}
//設(shè)置線程池同時最大線程數(shù)
[self.queue setMaxConcurrentOperationCount:3];
customOperation *customA = [[customOperation alloc] initWithName:@"customA"];
customOperation *customB = [[customOperation alloc] initWithName:@"customB"];
customOperation *customC = [[customOperation alloc] initWithName:@"customC"];
customOperation *customD = [[customOperation alloc] initWithName:@"customD"];
[customD addDependency:customA];
[customA addDependency:customC];
[customC addDependency:customB];
//下面這句不能加不然會死鎖
// [customB addDependency:customD];
[self.queue addOperation:customA];
[self.queue addOperation:customB];
[self.queue addOperation:customC];
[self.queue addOperation:customD];
NSLog(@"end");
}
控制臺打印結(jié)果:
2017-02-23 20:08:05.766 pThreadTest[3722:378340] main thread<NSThread: 0x600000079180>{number = 1, name = main}
2017-02-23 20:08:05.767 pThreadTest[3722:378340] end
2017-02-23 20:08:06.841 pThreadTest[3722:378589] customB
2017-02-23 20:08:06.841 pThreadTest[3722:378624] customD
2017-02-23 20:08:06.841 pThreadTest[3722:378591] customA
2017-02-23 20:08:06.841 pThreadTest[3722:378588] customC
//執(zhí)行操作并沒有按照 B C A D的順序執(zhí)行,這是因為main函數(shù)中打印的操作是放在異步線程中執(zhí)行的。
解決上面的問題:
//
// customOperation.m
// pThreadTest
//
// Created by Dustin on 17/2/23.
// Copyright ? 2017年 Dustin. All rights reserved.
//
#import "customOperation.h"
@interface customOperation()
@property (nonatomic,copy)NSString *operName;
@property (nonatomic,assign)BOOL over;
@end
@implementation customOperation
- (instancetype)initWithName:(NSString *)name{
if (self = [super init]) {
self.operName = name;
}
return self;
}
//重寫main方法
- (void)main{
// for (int i = 0; i < 3; i ++) {
// NSLog(@"%@ %d",self.operName,i);
// [NSThread sleepForTimeInterval:1];
// }
//異步進(jìn)行耗時操作
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSThread sleepForTimeInterval:1];
if (self.cancelled) {
return ;
}
NSLog(@"%@",self.operName);
self.over = YES;
});
//利用runloop保持線程在沒執(zhí)行完時不會結(jié)束
while (!self.over && !self.cancelled) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
@end
控制臺打印結(jié)果:
2017-02-23 20:16:01.330 pThreadTest[3744:384234] main thread<NSThread: 0x60800007c140>{number = 1, name = main}
2017-02-23 20:16:01.330 pThreadTest[3744:384234] end
2017-02-23 20:16:02.394 pThreadTest[3744:384384] customB
2017-02-23 20:16:03.459 pThreadTest[3744:384383] customC
2017-02-23 20:16:04.535 pThreadTest[3744:384384] customA
2017-02-23 20:16:05.574 pThreadTest[3744:384386] customD
//這樣依賴的順序就正確了
關(guān)于Runloop這里就不做過多介紹,可以從網(wǎng)上看一些資料。
總結(jié):關(guān)于多線程暫時就總結(jié)到這里,如果哪里我寫的不夠準(zhǔn)確或者有遺漏,還請看過的朋友多多交流。