這篇文章對iOS多線程技術(shù)NSOperation的常用方法做了簡單總結(jié)
GCD請見這篇
本文代碼
NSOperation:
- 簡介:
- 是蘋果在GCD的基礎(chǔ)上做了一次面向?qū)ο蟮姆庋b
- 核心概念和GCD很像
- NSOperation是一個抽象類,想要封裝操作,需要使用子類
- 可以自定義子類,繼承NSOperation,實現(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一樣
想要開線程,必須把任務加到隊列中去
因為NSOperation底層是GCD,GCD就是把任務加入到隊列,根據(jù)隊列和函數(shù)的情況來決定是否開啟線程
-
NSBlockOperation:
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載1------%@", [NSThread currentThread]);
}];
//添加額外任務
[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í)行任務
但是如果通過addExecutionBlock添加了新任務
那么新任務會在子線程執(zhí)行(這里是比較特殊的地方)
- NSOperationQueue:
NSOperation可以用start方法來執(zhí)行任務,但是默認是同步的
如果將NSOperation添加到NSOperationQueue中,系統(tǒng)會自動異步執(zhí)行NSOperation中的操作
//創(chuàng)建隊列
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有兩種隊列:
- 主隊列:
[NSOperationQueue mainQueue];
凡是添加到主隊列中的任務,都會在主線程執(zhí)行
- 非主隊列
[[NSOperationQueue alloc]init];
同時包含了串行和并發(fā)的功能
就像上面的代碼,把任務添加到隊列以后,自動在子線程中執(zhí)行
如果想用非主線程實現(xiàn)串行,則需要設置并發(fā)量
- 其他用法:
-
最大并發(fā)數(shù):
// 設置最大并發(fā)操作數(shù)
queue.maxConcurrentOperationCount = 2;
設置了最大并發(fā)數(shù),就是限制了系統(tǒng)開線程的數(shù)量
如果設置為1
任務就會串行執(zhí)行
-
掛起:
self.queue.suspended = YES;
當隊列中的任務正在執(zhí)行的時候,設置掛起為YES
任務會暫停
但是沒有從內(nèi)存中銷毀
當設置為NO的時候,任務會繼續(xù)進行
-
取消所有任務:
[self.queue cancelAllOperations];
調(diào)用queue的cancelAllOperations方法
相當于調(diào)用了queue中每個NSOperation的cancel方法
所有的任務就被取消了
如果想繼續(xù)任務
需要重新定制任務加入隊列
但是需要注意的是:
如果調(diào)用cancel方法的時候,NSOperation有一個任務正在執(zhí)行
那么需要這個任務執(zhí)行完了以后,再實現(xiàn)cancel
-
自定義NSOperation:
繼承NSOperation類
可以實現(xiàn)自定義NSOperation
需要執(zhí)行的任務在main方法中實現(xiàn)
#import "HXOperation.h"
@implementation HXOperation
/**
* 需要執(zhí)行的任務
*/
-(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)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//添加NSOperation
[queue addOperation:[[HXOperation alloc] init]];
這樣就會自動執(zhí)行main中的任務了
但是蘋果建議我們自定義NSOperation的時候,如果內(nèi)部有耗時操作
那么應該在每一個耗時操作結(jié)束以后,檢查一下當前任務有沒有被取消
因為很有可能NSOperation在執(zhí)行任務的時候,外部調(diào)用了他的cancel方法
如果被取消了就不要再執(zhí)行剩下的任務了:
if (self.isCancelled) return;
-
任務依賴和任務監(jiān)聽
我們知道把多個任務加入隊列后
系統(tǒng)從隊列中把任務取出來并發(fā)執(zhí)行
但是誰先執(zhí)行取決于CPU先調(diào)度那條線程
當我們需要執(zhí)行某個任務之后再執(zhí)行其他任務的話(比如下載完圖片后再對圖片進行處理)
那么就需要設置依賴
或者可以對任務做監(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)聽任務執(zhí)行完畢后 進行其他操作
op5.completionBlock = ^{
NSLog(@"op5執(zhí)行完畢---%@", [NSThread currentThread]);
};
// 設置依賴
[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)絡路徑
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)絡路徑
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;
}];
}];
//設置依賴 保證下載完圖片 再去合成
[combine addDependency:download1];
[combine addDependency:download2];
[queue addOperation:download1];
[queue addOperation:download2];
[queue addOperation:combine];
感謝閱讀
你的支持是我寫作的唯一動力