這篇文章對iOS多線程技術(shù)NSOperation的常用方法做了簡單總結(jié)
GCD請見這篇
本文代碼
NSOperation:
- 簡介:
- 是蘋果在GCD的基礎(chǔ)上做了一次面向?qū)ο蟮姆庋b
- 核心概念和GCD很像
- NSOperation是一個抽象類,想要封裝操作,需要使用子類
- 可以自定義子類,繼承NSOperation,實(shí)現(xiàn)多線程操作
- NSOperation的子類:
-
NSInvocationOperation:
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];
[op start];
打印結(jié)果:
------<NSThread: 0x600000260ac0>{number = 1, name = main}
結(jié)論:
如果僅僅是封裝了操作,然后調(diào)用start方法
他是不會開線程的
就像調(diào)用了performSelectorr一樣
想要開線程,必須把任務(wù)加到隊(duì)列中去
因?yàn)镹SOperation底層是GCD,GCD就是把任務(wù)加入到隊(duì)列,根據(jù)隊(duì)列和函數(shù)的情況來決定是否開啟線程
-
NSBlockOperation:
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載1------%@", [NSThread currentThread]);
}];
//添加額外任務(wù)
[op addExecutionBlock:^{
NSLog(@"下載2------%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"下載3------%@", [NSThread currentThread]);
}];
[op start];
打印結(jié)果:
下載1------<NSThread: 0x60800006f180>{number = 1, name = main}
下載2------<NSThread: 0x6000000791c0>{number = 3, name = (null)}
下載3------<NSThread: 0x608000079e00>{number = 4, name = (null)}
結(jié)論:
和NSInvocationOperation一樣
如果只是封裝了操作并調(diào)用了start方法
只是會在主線程執(zhí)行任務(wù)
但是如果通過addExecutionBlock添加了新任務(wù)
那么新任務(wù)會在子線程執(zhí)行(這里是比較特殊的地方)
- NSOperationQueue:
NSOperation可以用start方法來執(zhí)行任務(wù),但是默認(rèn)是同步的
如果將NSOperation添加到NSOperationQueue中,系統(tǒng)會自動異步執(zhí)行NSOperation中的操作
//創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//創(chuàng)建操作(NSInvocationOperation)
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download2) object:nil];
//創(chuàng)建操作(NSBlockOperation)
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download3 --- %@", [NSThread currentThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"download4 --- %@", [NSThread currentThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"download5 --- %@", [NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download6 --- %@", [NSThread currentThread]);
}];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
打印結(jié)果:
download6 --- <NSThread: 0x608000269080>{number = 6, name = (null)}
download2 --- <NSThread: 0x600000265240>{number = 4, name = (null)}
download1 --- <NSThread: 0x600000265200>{number = 3, name = (null)}
download3 --- <NSThread: 0x6000002655c0>{number = 5, name = (null)}
download4 --- <NSThread: 0x608000265f80>{number = 7, name = (null)}
download5 --- <NSThread: 0x600000265300>{number = 8, name = (null)}
結(jié)論:
NSOperation有兩種隊(duì)列:
- 主隊(duì)列:
[NSOperationQueue mainQueue];
凡是添加到主隊(duì)列中的任務(wù),都會在主線程執(zhí)行
- 非主隊(duì)列
[[NSOperationQueue alloc]init];
同時包含了串行和并發(fā)的功能
就像上面的代碼,把任務(wù)添加到隊(duì)列以后,自動在子線程中執(zhí)行
如果想用非主線程實(shí)現(xiàn)串行,則需要設(shè)置并發(fā)量
- 其他用法:
-
最大并發(fā)數(shù):
// 設(shè)置最大并發(fā)操作數(shù)
queue.maxConcurrentOperationCount = 2;
設(shè)置了最大并發(fā)數(shù),就是限制了系統(tǒng)開線程的數(shù)量
如果設(shè)置為1
任務(wù)就會串行執(zhí)行
-
掛起:
self.queue.suspended = YES;
當(dāng)隊(duì)列中的任務(wù)正在執(zhí)行的時候,設(shè)置掛起為YES
任務(wù)會暫停
但是沒有從內(nèi)存中銷毀
當(dāng)設(shè)置為NO的時候,任務(wù)會繼續(xù)進(jìn)行
-
取消所有任務(wù):
[self.queue cancelAllOperations];
調(diào)用queue的cancelAllOperations方法
相當(dāng)于調(diào)用了queue中每個NSOperation的cancel方法
所有的任務(wù)就被取消了
如果想繼續(xù)任務(wù)
需要重新定制任務(wù)加入隊(duì)列
但是需要注意的是:
如果調(diào)用cancel方法的時候,NSOperation有一個任務(wù)正在執(zhí)行
那么需要這個任務(wù)執(zhí)行完了以后,再實(shí)現(xiàn)cancel
-
自定義NSOperation:
繼承NSOperation類
可以實(shí)現(xiàn)自定義NSOperation
需要執(zhí)行的任務(wù)在main方法中實(shí)現(xiàn)
#import "HXOperation.h"
@implementation HXOperation
/**
* 需要執(zhí)行的任務(wù)
*/
-(void)main
{
for (NSInteger i = 0; i<1000; i++) {
NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
for (NSInteger i = 0; i<1000; i++) {
NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
for (NSInteger i = 0; i<1000; i++) {
NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
}
if (self.isCancelled) return;
}
@end
在外部這么使用就可以:
// 創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//添加NSOperation
[queue addOperation:[[HXOperation alloc] init]];
這樣就會自動執(zhí)行main中的任務(wù)了
但是蘋果建議我們自定義NSOperation的時候,如果內(nèi)部有耗時操作
那么應(yīng)該在每一個耗時操作結(jié)束以后,檢查一下當(dāng)前任務(wù)有沒有被取消
因?yàn)楹苡锌赡躈SOperation在執(zhí)行任務(wù)的時候,外部調(diào)用了他的cancel方法
如果被取消了就不要再執(zhí)行剩下的任務(wù)了:
if (self.isCancelled) return;
-
任務(wù)依賴和任務(wù)監(jiān)聽
我們知道把多個任務(wù)加入隊(duì)列后
系統(tǒng)從隊(duì)列中把任務(wù)取出來并發(fā)執(zhí)行
但是誰先執(zhí)行取決于CPU先調(diào)度那條線程
當(dāng)我們需要執(zhí)行某個任務(wù)之后再執(zhí)行其他任務(wù)的話(比如下載完圖片后再對圖片進(jìn)行處理)
那么就需要設(shè)置依賴
或者可以對任務(wù)做監(jiān)聽
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download1----%@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download2----%@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download3----%@", [NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
for (NSInteger i = 0; i<10; i++) {
NSLog(@"download4----%@", [NSThread currentThread]);
}
}];
NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download5----%@", [NSThread currentThread]);
}];
//監(jiān)聽任務(wù)執(zhí)行完畢后 進(jìn)行其他操作
op5.completionBlock = ^{
NSLog(@"op5執(zhí)行完畢---%@", [NSThread currentThread]);
};
// 設(shè)置依賴
[op3 addDependency:op1];
[op3 addDependency:op2];
[op3 addDependency:op4];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
[queue addOperation:op5];
-
線程之間的通信:
在子線程下載圖片后,回到主線程更新UI:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
__block UIImage *image1 = nil;
// 下載圖片1
NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{
// 圖片的網(wǎng)絡(luò)路徑
NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
// 加載圖片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成圖片
image1 = [UIImage imageWithData:data];
}];
__block UIImage *image2 = nil;
// 下載圖片2
NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
// 圖片的網(wǎng)絡(luò)路徑
NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
// 加載圖片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成圖片
image2 = [UIImage imageWithData:data];
}];
// 合成圖片
NSBlockOperation *combine = [NSBlockOperation blockOperationWithBlock:^{
// 開啟新的圖形上下文
UIGraphicsBeginImageContext(CGSizeMake(100, 100));
// 繪制圖片
[image1 drawInRect:CGRectMake(0, 0, 50, 100)];
image1 = nil;
[image2 drawInRect:CGRectMake(50, 0, 50, 100)];
image2 = nil;
// 取得上下文中的圖片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 結(jié)束上下文
UIGraphicsEndImageContext();
// 回到主線程顯示圖片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}];
//設(shè)置依賴 保證下載完圖片 再去合成
[combine addDependency:download1];
[combine addDependency:download2];
[queue addOperation:download1];
[queue addOperation:download2];
[queue addOperation:combine];
感謝閱讀
你的支持是我寫作的唯一動力