一汽摹、 為什么要使用 NSOperation、NSOperationQueue
NSOperation苦锨、NSOperationQueue 是基于 GCD 更高一層的封裝逼泣,完全面向?qū)ο蟆5潜?GCD 更簡單易用舟舒、代碼可讀性也更高拉庶。
可添加完成的代碼塊,在操作完成后執(zhí)行秃励。
添加操作之間的依賴關(guān)系氏仗,方便的控制執(zhí)行順序。
設(shè)定操作執(zhí)行的優(yōu)先級夺鲜。
可以很方便的取消一個操作的執(zhí)行廓鞠。
使用 KVO 觀察對操作執(zhí)行狀態(tài)的更改:isExecuteing、isFinished谣旁、isCancelled。
二滋早、同步異步以及并行串行的區(qū)別
同步就是指一個進程在執(zhí)行某個請求的時候榄审,若該請求需要一段時間才能返回信息,那么這個進程將會一直等待下去杆麸,直到收到返回信息才繼續(xù)執(zhí)行下去搁进;異步是指進程不需要一直等下去浪感,而是繼續(xù)執(zhí)行下面的操作,不管其他進程的狀態(tài)
并行串行是指有多個線程的任務(wù)的執(zhí)行方式饼问,串行則多個任務(wù)按順序執(zhí)行影兽,并行則多個任務(wù)會同時執(zhí)行
三、 NSOperation 實現(xiàn)多線程的使用步驟分為三步:
NSOperation 需要配合 NSOperationQueue 來實現(xiàn)多線程莱革。因為默認情況下峻堰,NSOperation 單獨使用時系統(tǒng)同步執(zhí)行操作,配合 NSOperationQueue 我們能更好的實現(xiàn)異步執(zhí)行盅视。
1捐名、創(chuàng)建操作:先將需要執(zhí)行的操作封裝到一個NSOperation 對象中。
2闹击、創(chuàng)建隊列:創(chuàng)建NSOperationQueue對象镶蹋。
3、將操作加入到隊列中:將 NSOperation 對象添加到 NSOperationQueue 對象中赏半。
之后呢贺归,系統(tǒng)就會自動將 NSOperationQueue 中的 NSOperation 取出來,在新線程中執(zhí)行操作断箫。
四、線程死鎖
// NSLog(@"1");
// dispatch_sync(dispatch_get_main_queue(), ^{
// NSLog(@"2");
// });
// NSLog(@"3");
主線程是串行隊列, 一個任務(wù)執(zhí)行完成才能往下執(zhí)行, 同步線程是一個任務(wù)A ,里面的block是同步線程插入到當(dāng)前線程的另一個任務(wù)B ,A要等B執(zhí)行完才返回 ,B要等A執(zhí)行完才能執(zhí)行 ,會相互等待 造成死鎖
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 創(chuàng)建操作
// [self useInvocationOperation];
//// NSOperation單獨使用 同步執(zhí)行 等待結(jié)果返回后執(zhí)行后面語句
// NSLog(@"下一個語句");
// [self useBlockOperation];
// 創(chuàng)建隊列
// [self addOperationToQueue];
// 添加依賴
// [self addDependency];
// 優(yōu)先級
// [self queuePriority];
// 線程間通訊
// [self communication];
// 線程死鎖:主線程是串行隊列 一個任務(wù)執(zhí)行完成才能往下執(zhí)行 同步線程是一個任務(wù)A 里面的block是同步線程插入到當(dāng)前線程的另一個任務(wù)B A要等B執(zhí)行完才返回 B要等A執(zhí)行完才能執(zhí)行 會相互等待 造成死鎖
// NSLog(@"1");
// dispatch_sync(dispatch_get_main_queue(), ^{
// NSLog(@"2");
// });
// NSLog(@"3");
}
#pragma mark - 創(chuàng)建操作
//NSOperation 單獨使用時系統(tǒng)同步執(zhí)行操作
//NSOperation 是個抽象類踱葛,不能用來封裝操作。我們只有使用它的子類來封裝操作尸诽。我們有三種方式來封裝操作。
//使用子類 NSInvocationOperation
//使用子類 NSBlockOperation
//自定義繼承自 NSOperation 的子類性含,通過實現(xiàn)內(nèi)部相應(yīng)的方法來封裝操作。
- (void)useInvocationOperation{
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationTask) object:nil];
// 開始操作
[invocationOperation start];
}
//NSBlockOperation 還提供了一個方法 addExecutionBlock:商蕴,通過 addExecutionBlock: 就可以為 NSBlockOperation 添加額外的操作。這些操作(包括 blockOperationWithBlock 中的操作)可以在不同的線程中同時(并發(fā))執(zhí)行(完成順序是不一定的)绪商。只有當(dāng)所有相關(guān)的操作已經(jīng)完成執(zhí)行時,才視為完成
//一般情況下格郁,如果一個 NSBlockOperation 對象封裝了多個操作。NSBlockOperation 是否開啟新線程例书,取決于操作的個數(shù)锣尉。如果添加的操作的個數(shù)多,就會自動開啟新線程决采。當(dāng)然開啟的線程數(shù)是由系統(tǒng)來決定的自沧。
- (void)useBlockOperation{
NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"blockOperation---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
// 2.添加額外的操作
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"5---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"6---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"7---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[blockOperation addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"8---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
// 調(diào)用 start 方法開始執(zhí)行操作
[blockOperation start];
}
- (void)useCustomOperation{
}
#pragma mark - 創(chuàng)建隊列
//NSOperationQueue 創(chuàng)建的自定義隊列同時具有串行、并發(fā)功能树瞭,通過設(shè)置屬性maxConcurrentOperationCount(最大并發(fā)操作數(shù))用來控制一個特定隊列中可以有多少個操作同時參與并發(fā)執(zhí)行拇厢。
//當(dāng)最大并發(fā)操作數(shù)為1時,操作是按順序串行執(zhí)行的移迫,并且一個操作完成之后旺嬉,下一個操作才開始執(zhí)行。當(dāng)最大操作并發(fā)數(shù)為2時厨埋,操作是并發(fā)執(zhí)行的邪媳,可以同時執(zhí)行兩個操作。而開啟線程數(shù)量是由系統(tǒng)決定的荡陷,不需要我們來管理
//那么我們需要將創(chuàng)建好的操作加入到隊列中去雨效。總共有兩種方法:
//1废赞、- (void)addOperation:(NSOperation *)op;
//2徽龟、- (void)addOperationWithBlock:(void (^)(void))block;
/**
* 使用 addOperation: 將操作加入到操作隊列中
*/
- (void)addOperationToQueue {
// 1.創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 設(shè)置最大并發(fā)操作數(shù) 來控制隊列的串行或者并發(fā)
queue.maxConcurrentOperationCount = 1;
// queue.maxConcurrentOperationCount = 8;
// 2.創(chuàng)建操作
// 使用 NSInvocationOperation 創(chuàng)建操作1
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationTask) object:nil];
// 使用 NSInvocationOperation 創(chuàng)建操作2
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationTask2) object:nil];
op1.queuePriority = NSOperationQueuePriorityHigh;
op2.queuePriority = NSOperationQueuePriorityLow;
// 使用 NSBlockOperation 創(chuàng)建操作3
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[op3 addExecutionBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"4---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
// 3.使用 addOperation: 添加所有操作到隊列中
[queue addOperation:op1]; // [op1 start] 添加到隊列中 無需再自己調(diào)用start
[queue addOperation:op2]; // [op2 start]
[queue addOperation:op3]; // [op3 start]
}
/**
* 使用 addOperationWithBlock: 將操作加入到操作隊列中
無需自己創(chuàng)建操作對象 直接將操作放到block中
*/
- (void)addOperationWithBlockToQueue {
// 1.創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.使用 addOperationWithBlock: 添加操作到隊列中
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
[queue addOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
}
#pragma mark - NSOperation 操作依賴
//NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之間的依賴關(guān)系唉地。通過操作依賴据悔,我們可以很方便的控制操作之間的執(zhí)行先后順序。NSOperation 提供了3個接口供我們管理和查看依賴耘沼。
//- (void)addDependency:(NSOperation *)op; 添加依賴极颓,使當(dāng)前操作依賴于操作 op 的完成。
//- (void)removeDependency:(NSOperation *)op; 移除依賴群嗤,取消當(dāng)前操作對操作 op 的依賴菠隆。
//@property (readonly, copy) NSArray<NSOperation *> *dependencies; 在當(dāng)前操作開始執(zhí)行之前完成執(zhí)行的所有操作對象數(shù)組。
- (void)addDependency {
// 1.創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.創(chuàng)建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
// 3.添加依賴
[op2 addDependency:op1]; // 讓op2 依賴于 op1狂秘,則先執(zhí)行op1骇径,在執(zhí)行op2
// 4.添加操作到隊列中
[queue addOperation:op1];
[queue addOperation:op2];
}
#pragma mark - NSOperation 優(yōu)先級 & 服務(wù)質(zhì)量(只是盡可能的 并不是絕對的)
//NSOperation 提供了queuePriority(優(yōu)先級)屬性,queuePriority屬性適用于同一操作隊列中的操作者春,不適用于不同操作隊列中的操作破衔。默認情況下,所有新創(chuàng)建的操作對象優(yōu)先級都是NSOperationQueuePriorityNormal运敢。但是我們可以通過setQueuePriority:方法來改變當(dāng)前操作在同一隊列中的執(zhí)行優(yōu)先級。
//對于添加到隊列中的操作传惠,首先進入準備就緒的狀態(tài)(就緒狀態(tài)取決于操作之間的依賴關(guān)系)稻扬,然后進入就緒狀態(tài)的操作的開始執(zhí)行順序(非結(jié)束執(zhí)行順序)由操作之間相對的優(yōu)先級決定(優(yōu)先級是操作對象自身的屬性)
//準備就緒狀態(tài):當(dāng)一個操作的所有依賴都已經(jīng)完成時泰佳,操作對象通常會進入準備就緒狀態(tài),等待執(zhí)行
//queuePriority 屬性(盡可能的并不是絕對的)決定了進入準備就緒狀態(tài)下的操作之間的開始執(zhí)行順序浇坐。并且黔宛,優(yōu)先級不能取代依賴關(guān)系。
//如果一個隊列中既包含高優(yōu)先級操作觉渴,又包含低優(yōu)先級操作徽惋,并且兩個操作都已經(jīng)準備就緒,那么隊列先執(zhí)行高優(yōu)先級操作踢京。比如上例中宦棺,如果 op1 和 op4 是不同優(yōu)先級的操作渺氧,那么就會先執(zhí)行優(yōu)先級高的操作。
//如果白华,一個隊列中既包含了準備就緒狀態(tài)的操作贩耐,又包含了未準備就緒的操作,未準備就緒的操作優(yōu)先級比準備就緒的操作優(yōu)先級高管搪。那么,雖然準備就緒的操作優(yōu)先級低霎箍,也會優(yōu)先執(zhí)行澡为。優(yōu)先級不能取代依賴關(guān)系。如果要控制操作間的啟動順序顶别,則必須使用依賴關(guān)系
- (void)queuePriority{
// 1.創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 設(shè)置最大并發(fā)操作數(shù) 來控制隊列的串行或者并發(fā)
// queue.maxConcurrentOperationCount = 1; //串行
// queue.maxConcurrentOperationCount = 8;//并發(fā)
// 2.創(chuàng)建操作
// 使用 NSInvocationOperation 創(chuàng)建操作1
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationTask) object:nil];
// 使用 NSInvocationOperation 創(chuàng)建操作2
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationTask2) object:nil];
// 使用 NSInvocationOperation 創(chuàng)建操作2
NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationTask3) object:nil];
// 同一個隊列中的操作優(yōu)先級 執(zhí)行的先后 并不是結(jié)束的先后
// op1.queuePriority = NSOperationQueuePriorityVeryHigh;
// op2.queuePriority = NSOperationQueuePriorityHigh;
// op3.queuePriority = NSOperationQueuePriorityNormal;
// 服務(wù)質(zhì)量 在iOS 8.0前,通過設(shè)置操作的優(yōu)先級,盡可能的保證某個操作優(yōu)先處理,隨著硬件性能上的提升,通過設(shè)置優(yōu)先級效果已經(jīng)越來越不明顯,在iOS 8.0后,推出了服務(wù)質(zhì)量,通過設(shè)置服務(wù)質(zhì)量,讓系統(tǒng)優(yōu)先處理某一個操作 目前也是越來越不明顯
// op1.qualityOfService = NSQualityOfServiceUserInteractive;
// op2.qualityOfService = NSQualityOfServiceBackground;
// op3.qualityOfService = NSQualityOfServiceDefault;
// 3.使用 addOperation: 添加所有操作到隊列中
[queue addOperation:op1]; // [op1 start] 添加到隊列中 無需再自己調(diào)用start
[queue addOperation:op2]; // [op2 start]
[queue addOperation:op3]; // [op3 start]
}
#pragma mark - NSOperation驯绎、NSOperationQueue 線程間的通信
/**
* 線程間通信
*/
- (void)communication {
// 1.創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 2.添加操作
[queue addOperationWithBlock:^{
// 異步進行耗時操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
// 回到主線程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 進行一些 UI 刷新等操作
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
}
}];
}];
}
#pragma mark - 執(zhí)行的操作
- (void)invocationTask{
NSLog(@"1");
for (int i = 0; i < 2; i++) {
NSLog(@"invocationOperation1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
}
}
- (void)invocationTask2{
NSLog(@"2");
for (int i = 0; i < 2; i++) {
NSLog(@"invocationOperation2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
}
}
- (void)invocationTask3{
NSLog(@"3");
for (int i = 0; i < 2; i++) {
NSLog(@"invocationOperation3---%@", [NSThread currentThread]); // 打印當(dāng)前線程
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
}
}
@end
個人博客地址:https://youyou0909.github.io