本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末

如果觉得本文内容过长,请前往本人"简书"

本文源码 Demo 详见 Github

https://github.com/shorfng/iOS-4.0-multithreading.git

1.0 NSOperation 的作用

使用 NSOperation 的目的就是为了让开发人员不再关心线程

  • 配合使用 NSOperation(任务) 和 NSOperationQueue(队列) 也能实现多线程编程

NSOperation 和 NSOperationQueue 实现多线程的具体步骤:

(1)先将需要执行的操作封装到一个NSOperation对象中

(2)然后将NSOperation对象添加到NSOperationQueue中

(3)系统会自动将NSOperationQueue中的NSOperation取出来

(4)将取出的NSOperation封装的操作放到一条新线程中执行

使用NSOperation子类的方式有3种:

NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类

  1. NSInvocationOperation
  • NSBlockOperation
  • 自定义子类继承NSOperation,实现内部相应的方法

2.0 NSInvocationOperation

//创建NSInvocationOperation对象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg; //调用start方法开始执行操作,一旦执行操作,就会调用target的sel方法
- (void)start;

注意:

  • 默认情况下,操作对象在主线程中执行
  • 调用了start方法后并不会开一条新线程去执行操作,只有添加到队列中才会开启新的线程
  • 即默认情况下,如果操作没有放到队列中queue中,都是同步执行。
  • 只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作

代码示例:

#import "ViewController.h"

@interface ViewController ()
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { //创建操作对象,封装要执行的任务
NSInvocationOperation *op =
[[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(run)
object:nil];
//执行操作
[op start];
} - (void)run {
NSLog(@"------%@", [NSThread currentThread]);
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end

打印结果:

NSInvocationOperation[862:29437] ------<NSThread: 0x7f9cea507920>{number = 1, name = main}

3.0 NSBlockOperation

//创建 NSBlockOperation 操作对象
+ (id)blockOperationWithBlock:(void (^)(void))block; // 添加操作
- (void)addExecutionBlock:(void (^)(void))block;

注意:只要NSBlockOperation封装的操作数 > 1,就会异步执行操作


代码示例:

#import "ViewController.h"

@interface ViewController ()
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 1.创建 NSBlockOperation 操作对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// 在主线程
NSLog(@"下载1------%@", [NSThread currentThread]);
}]; // 2.添加操作(额外的任务)(在子线程执行)
[op addExecutionBlock:^{
NSLog(@"下载2------%@", [NSThread currentThread]);
}]; [op addExecutionBlock:^{ [op addExecutionBlock:^{
NSLog(@"下载2------%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"下载3------%@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"下载4------%@", [NSThread currentThread]);
}];
// 3.开启执行操作
[op start];
}
- (void)run {
NSLog(@"------%@", [NSThread currentThread]);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end

打印结果:

NSBlockOperation[1013:37922] 下载1------<NSThread: 0x7feea1c05460>{number = 1, name = main}
NSBlockOperation[1013:37952] 下载2------<NSThread: 0x7feea1f0b790>{number = 2, name = (null)}
NSBlockOperation[1013:37955] 下载3------<NSThread: 0x7feea1c0f8a0>{number = 3, name = (null)}
NSBlockOperation[1013:37951] 下载4------<NSThread: 0x7feea1e0b520>{number = 4, name = (null)}

4.0 NSOperationQueue

NSOperationQueue的作用:添加操作到NSOperationQueue中,自动执行操作,自动开启线程

  • NSOperation 可以调用 start 方法来执行任务,但默认是同步执行的
  • 如果将 NSOperation 添加到 NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

添加操作到 NSOperationQueue 中:2种方式


- (void)addOperation:(NSOperation *)op; - (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);

代码示例:

#import "ViewController.h"

@interface ViewController ()
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self operationQueue2];
} #pragma mark - 把操作添加到队列中,方式1:addOperation
- (void)operationQueue1 {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.1 方式1:创建操作(任务)NSInvocationOperation ,封装操作
NSInvocationOperation *op1 =
[[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(download1)
object:nil]; // 2.2 方式2:创建NSBlockOperation ,封装操作
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download2 --- %@", [NSThread currentThread]);
}]; // 添加操作
[op2 addExecutionBlock:^{
NSLog(@"download3 --- %@", [NSThread currentThread]);
}]; // 3.把操作(任务)添加到队列中,并自动调用 start 方法
[queue addOperation:op1];
[queue addOperation:op2];
} - (void)download1 {
NSLog(@"download1 --- %@", [NSThread currentThread]);
} #pragma mark - 把操作添加到队列中,方式2:addOperationWithBlock
- (void)operationQueue2 {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.添加操作到队列中
[queue addOperationWithBlock:^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"download2 --- %@", [NSThread currentThread]);
}];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

打印结果:

 NSOperationQueue[1658:89517] download2 --- <NSThread: 0x7f88a9e059d0>{number = 3, name = (null)}
NSOperationQueue[1658:89518] download1 --- <NSThread: 0x7f88a9d901f0>{number = 2, name = (null)}
NSOperationQueue[1658:89521] download3 --- <NSThread: 0x7f88a9d15d30>{number = 4, name = (null)} NSOperationQueue[1704:92509] download2 --- <NSThread: 0x7fd318f06540>{number = 2, name = (null)}
NSOperationQueue[1704:92513] download1 --- <NSThread: 0x7fd318d0e460>{number = 3, name = (null)}

提示:队列的取出是有顺序的,与打印结果并不矛盾。这就好比,选手A,BC虽然起跑的顺序是先A,后B,然后C,但是到达终点的顺序却不一定是A,B在前,C在后。

4.1 最大并发数

并发数:同时执⾏行的任务数 比如,同时开3个线程执行3个任务,并发数就是3

最大并发数:同一时间最多只能执行的任务的个数

最⼤并发数的相关⽅方法:

//最大并发数,默认为-1
@property NSInteger maxConcurrentOperationCount; - (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

说明:

  • 如果没有设置最大并发数,那么并发的个数是由系统内存和CPU决定的,内存多就开多一点,内存少就开少一点。
  • 最⼤并发数的值并不代表线程的个数,仅仅代表线程的ID。
  • 最大并发数不要乱写(5以内),不要开太多,一般以2~3为宜,因为虽然任务是在子线程进行处理的,但是cpu处理这些过多的子线程可能会影响UI,让UI变卡。
  • 最大并发数的值为1,就变成了串行队列

代码示例:

#import "ViewController.h"

@interface ViewController ()
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)
queue.maxConcurrentOperationCount = 2; // 3.添加操作
[queue addOperationWithBlock:^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
NSLog(@"download2 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
NSLog(@"download3 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
NSLog(@"download4 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
NSLog(@"download5 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
NSLog(@"download6 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.01];
}];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

打印结果:

最大并发数[1909:113433] download2 --- <NSThread: 0x7ffef240ba70>{number = 3, name = (null)}
最大并发数[1909:113432] download1 --- <NSThread: 0x7ffef24aee50>{number = 2, name = (null)}
最大并发数[1909:113432] download4 --- <NSThread: 0x7ffef24aee50>{number = 2, name = (null)}
最大并发数[1909:113431] download3 --- <NSThread: 0x7ffef251aa80>{number = 4, name = (null)}
最大并发数[1909:113428] download5 --- <NSThread: 0x7ffef2603d90>{number = 5, name = (null)}
最大并发数[1909:113432] download6 --- <NSThread: 0x7ffef24aee50>{number = 2, name = (null)}

4.2 队列的暂停和恢复

队列的暂停:当前任务结束后,暂停执行下一个任务,而非当前任务

//暂停和恢复队列(YES代表暂停队列,NO代表恢复队列)
- (void)setSuspended:(BOOL)b; //当前状态
- (BOOL)isSuspended;

暂停和恢复的使用场合:

在tableview界面,开线程下载远程的网络界面,对UI会有影响,使用户体验变差。那么这种情况,就可以设置在用户操作UI(如滚动屏幕)的时候,暂停队列(不是取消队列),停止滚动的时候,恢复队列。


代码示例:

#import "ViewController.h"

@interface ViewController ()
@property(nonatomic, strong) NSOperationQueue *queue; //队列
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; // 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)
queue.maxConcurrentOperationCount = 1; // 3.添加操作
[queue addOperationWithBlock:^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:3];
}]; [queue addOperationWithBlock:^{
NSLog(@"download2 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:3];
}];
[queue addOperationWithBlock:^{
NSLog(@"download3 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:3];
}];
[queue addOperationWithBlock:^{
NSLog(@"download4 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:3];
}]; self.queue = queue;
} #pragma mark - 暂停和恢复
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.queue.isSuspended) {
self.queue.suspended = NO; // 恢复队列,继续执行
} else {
self.queue.suspended = YES; // 暂停(挂起)队列,暂停执行
}
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

打印结果:

队列的暂停和恢复[2650:156206] download1 --- <NSThread: 0x7fd689f552b0>{number = 3, name = (null)}
队列的暂停和恢复[2650:156205] download2 --- <NSThread: 0x7fd689c02e70>{number = 2, name = (null)}
队列的暂停和恢复[2650:156206] download3 --- <NSThread: 0x7fd689f552b0>{number = 3, name = (null)}
队列的暂停和恢复[2650:156385] download4 --- <NSThread: 0x7fd689ea11c0>{number = 4, name = (null)}

4.3 队列的取消

取消队列的所有操作:相等于调用了所有 NSOperation 的 -(void)cancel 方法,

当前任务结束后,取消执行下面的所有任务,而非当前任务

// 也可调用NSOperation的 -(void)cancel 方法取消单个操作
- (void)cancelAllOperations;

代码示例:

#import "ViewController.h"

@interface ViewController ()
@property(nonatomic, strong) NSOperationQueue *queue; //队列
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; // 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)
queue.maxConcurrentOperationCount = 1; // 3.添加操作
[queue addOperationWithBlock:^{
NSLog(@"download1 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:3];
}]; [queue addOperationWithBlock:^{
NSLog(@"download2 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:3];
}];
[queue addOperationWithBlock:^{
NSLog(@"download3 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:3];
}];
[queue addOperationWithBlock:^{
NSLog(@"download4 --- %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:3];
}]; self.queue = queue;
} #pragma mark - 取消队列的所有操作
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 取消队列的所有操作(相等于调用了所有NSOperation的-(void)cancel方法)
[self.queue cancelAllOperations];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

打印结果:

队列的取消[3041:167756] download1 --- <NSThread: 0x7fcc09543b40>{number = 3, name = (null)}
队列的取消[3041:167749] download2 --- <NSThread: 0x7fcc094505f0>{number = 2, name = (null)}

4.4 操作优先级

设置NSOperation在queue中的优先级,可以改变操作的执行优先级:

@property NSOperationQueuePriority queuePriority;

- (void)setQueuePriority:(NSOperationQueuePriority)p;

优先级的取值:优先级高的任务,调用的几率会更大

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};

4.5 操作依赖

NSOperation之间可以设置依赖来保证执行顺序:不能循环依赖(不能A依赖于B,B又依赖于A)

// 操作B依赖于操作A(一定要让操作A执行完后,才能执行操作B)
[operationB addDependency:operationA];

可以在不同queue的NSOperation之间创建依赖关系(跨队列依赖):

注意:

  • 一定要在把操作添加到队列之前,进行设置操作依赖。
  • 任务添加的顺序并不能够决定执行顺序,执行的顺序取决于依赖。

代码示例:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 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 < 5; i++) {
NSLog(@"download4----%@", [NSThread currentThread]);
}
}]; NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"download5----%@", [NSThread currentThread]);
}];
//操作的监听
op5.completionBlock = ^{
NSLog(@"op5执行完毕---%@", [NSThread currentThread]);
}; //设置操作依赖(op4执行完,才执行 op3)
[op3 addDependency:op1];
[op3 addDependency:op2];
[op3 addDependency:op4]; //把操作添加到队列中
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
[queue addOperation:op4];
[queue addOperation:op5];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

打印结果:

操作依赖[4196:150518] download5----<NSThread: 0x7ffa61d177d0>{number = 3, name = (null)}
操作依赖[4196:150506] download1----<NSThread: 0x7ffa61ca6b90>{number = 4, name = (null)}
操作依赖[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
操作依赖[4196:150510] download2----<NSThread: 0x7ffa61f0e800>{number = 5, name = (null)}
操作依赖[4196:150518] op5执行完毕---<NSThread: 0x7ffa61d177d0>{number = 3, name = (null)}
操作依赖[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
操作依赖[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
操作依赖[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
操作依赖[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
操作依赖[4196:150509] download3----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}

操作的监听

可以监听一个操作的执行完毕:

@property (nullable, copy) void (^completionBlock)(void);

- (void)setCompletionBlock:(void (^)(void))block;

代码详见4.5 操作依赖 示例代码

5.0 线程间通信(图片下载示例)

#import "ViewController.h"

@interface ViewController ()
@property(weak, nonatomic) IBOutlet UIImageView *imageView; @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self test2];
} #pragma mark - 线程间通信(图片合成)
- (void)test1 {
// 1.队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; __block UIImage *image1 = nil;
// 2.下载图片1
NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{
// 图片的网络路径
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;
// 3.下载图片2
NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
// 图片的网络路径
NSURL *url = [NSURL
URLWithString:
@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
// 加载图片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成图片
image2 = [UIImage imageWithData:data];
}]; // 4.合成图片
NSBlockOperation *combine = [NSBlockOperation blockOperationWithBlock:^{
// 开启新的图形上下文
UIGraphicsBeginImageContext(CGSizeMake(100, 100)); // 绘制图片1
[image1 drawInRect:CGRectMake(0, 0, 50, 100)];
image1 = nil; // 绘制图片2
[image2 drawInRect:CGRectMake(50, 0, 50, 100)];
image2 = nil; // 取得上下文中的图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // 结束上下文
UIGraphicsEndImageContext(); // 5.回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}]; // 设置依赖操作
[combine addDependency:download1];
[combine addDependency:download2]; //把操作添加到队列中
[queue addOperation:download1];
[queue addOperation:download2];
[queue addOperation:combine];
} #pragma mark - 线程间通信(图片下载)
- (void)test2 {
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
// 图片的网络路径
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]; // 生成图片
UIImage *image = [UIImage imageWithData:data]; // 回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

6.0 自定义NSOperation

自定义NSOperation的步骤很简单:

  • 重写- (void)main方法,在里面实现想执行的任务

重写- (void)main方法的注意点:

  • 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
  • 经常通过- (BOOL)isCancelled方法检测操作是否被取消,对取消做出响应

ViewController.m

#import "TDOperation.h"
#import "ViewController.h" @interface ViewController ()
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.创建自定义 TDGOperation
TDOperation *op = [[TDOperation alloc] init]; // 3.把操作(任务)添加到队列中,并自动调用 start 方法
[queue addOperation:op];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end

TDOperation.h(继承自:NSOperation)

#import <Foundation/Foundation.h>

@interface TDOperation : NSOperation
@end

TDOperation.m

#import "TDOperation.h"

@implementation TDOperation
//需要执行的任务
- (void)main {
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
}
// 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
if (self.isCancelled)
return; for (NSInteger i = 0; i < 3; i++) {
NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
}
// 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
if (self.isCancelled)
return; for (NSInteger i = 0; i < 3; i++) {
NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
}
// 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
if (self.isCancelled)
return;
}
@end

运行结果:

自定义NSOperation[1567:84075] download1 -0-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定义NSOperation[1567:84075] download1 -1-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定义NSOperation[1567:84075] download1 -2-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定义NSOperation[1567:84075] download2 -0-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定义NSOperation[1567:84075] download2 -1-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定义NSOperation[1567:84075] download2 -2-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定义NSOperation[1567:84075] download3 -0-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定义NSOperation[1567:84075] download3 -1-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定义NSOperation[1567:84075] download3 -2-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}

6.1 自定义NSOperation队列的取消操作

代码示例:

ViewController.m

#import "TDOperation.h"
#import "ViewController.h" @interface ViewController ()
@property(nonatomic, strong) NSOperationQueue *queue; //队列
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; // 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)
queue.maxConcurrentOperationCount = 2; // 3.添加操作 - 自定义 NSOperation
[queue addOperation:[[TDOperation alloc] init]]; self.queue = queue;
} #pragma mark - 取消队列的所有操作
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 取消队列的所有操作(相等于调用了所有NSOperation的-(void)cancel方法)
[self.queue cancelAllOperations];
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

TDOperation.h

#import <Foundation/Foundation.h>

@interface TDOperation : NSOperation

@end

TDOperation.m

#import "TDOperation.h"

@implementation TDOperation
//需要执行的任务
- (void)main {
for (NSInteger i = 0; i < 1000; i++) {
NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
}
// 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
if (self.isCancelled)
return; for (NSInteger i = 0; i < 1000; i++) {
NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
}
// 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
if (self.isCancelled)
return; for (NSInteger i = 0; i < 1000; i++) {
NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
}
// 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
if (self.isCancelled)
return;
}
@end

6.2 多图下载

沙盒结构:

Documents
Library
- Caches
- Preference
tmp

自定义NSOperation下载图片思路 – 有沙盒缓存


代码示例:

ViewController.m

#import "TDApp.h"
#import "ViewController.h" @interface ViewController ()
@property(nonatomic, strong) NSArray *apps; //所有数据
@property(nonatomic, strong) NSMutableDictionary *imageCache; //内存缓存的图片
@property(nonatomic, strong) NSOperationQueue *queue; //队列对象
@property(nonatomic, strong) NSMutableDictionary *operations; //所有的操作对象 @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} #pragma mark - 数据源方法
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return self.apps.count;
} #pragma mark - Cell
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 重用标识
static NSString *ID = @"app";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; TDApp *app = self.apps[indexPath.row]; #pragma mark - app 名称
cell.textLabel.text = app.name; #pragma mark - 下载量
cell.detailTextLabel.text = app.download; #pragma mark - 图片
// 1.先从内存缓存中取出图片
UIImage *image = self.imageCache[app.icon]; // 2.判断内存中是否有图片
if (image) {
// 2.1 内存中有图片,直接设置图片
cell.imageView.image = image;
} else {
// 2.2 内存中没有图片,将图片文件数据写入沙盒中 //(1)获得Library/Caches文件夹
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(
NSCachesDirectory, NSUserDomainMask, YES) firstObject];
//(2)获得文件名
NSString *filename = [app.icon lastPathComponent];
//(3)计算出文件的全路径
NSString *file = [cachesPath stringByAppendingPathComponent:filename];
//(4)加载沙盒的文件数据
NSData *data = [NSData dataWithContentsOfFile:file]; // 2.3 判断沙盒中是否有图片
if (data) { // 有图片,直接利用沙盒中图片,设置图片
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image;
// 并将图片存到字典中
self.imageCache[app.icon] = image; } else { // 没有图片,先设置一个占位图
cell.imageView.image = [UIImage imageNamed:@"placeholder"]; // 取出图片,并判断这张图片是否有下载操作
NSOperation *operation = self.operations[app.icon];
if (operation == nil) {
// 如果这张图片暂时没有下载操作,则需要创建一个下载操作
// 下载图片是耗时操作,放到子线程
operation = [NSBlockOperation blockOperationWithBlock:^{
// 下载图片
NSData *data =
[NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];
// 如果数据下载失败
if (data == nil) {
// 下载失败,移除操作
[self.operations removeObjectForKey:app.icon];
return;
} // 下载成功,将图片放在 image 中
UIImage *image = [UIImage imageWithData:data];
// 存到字典中
self.imageCache[app.icon] = image; //回到主线程显示图片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[tableView reloadRowsAtIndexPaths:@[ indexPath ]
withRowAnimation:UITableViewRowAnimationNone];
}]; // 将图片文件数据写入沙盒中
[data writeToFile:file atomically:YES];
// 下载完毕,移除操作
[self.operations removeObjectForKey:app.icon];
}]; // 添加到队列中(队列的操作不需要移除,会自动移除)
[self.queue addOperation:operation];
// 并将图片存到字典中
self.operations[app.icon] = operation;
}
}
} return cell;
} #pragma mark - 数据懒加载
- (NSArray *)apps {
if (!_apps) {
NSArray *dictArray =
[NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:@"apps.plist"
ofType:nil]]; NSMutableArray *appArray = [NSMutableArray array];
for (NSDictionary *dict in dictArray) {
[appArray addObject:[TDApp appWithDict:dict]];
}
_apps = appArray;
}
return _apps;
} #pragma mark - 懒加载
- (NSMutableDictionary *)imageCache {
if (!_imageCache) {
_imageCache = [NSMutableDictionary dictionary];
}
return _imageCache;
} #pragma mark - 懒加载
- (NSOperationQueue *)queue {
if (!_queue) {
_queue = [[NSOperationQueue alloc] init];
_queue.maxConcurrentOperationCount = 3;
}
return _queue;
} #pragma mark - 懒加载
- (NSMutableDictionary *)operations {
if (!_operations) {
_operations = [NSMutableDictionary dictionary];
}
return _operations;
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

TDApp.h

#import <Foundation/Foundation.h>

@interface TDApp : NSObject

@property(nonatomic, strong) NSString *icon;     // 图片
@property(nonatomic, strong) NSString *download; //下载量
@property(nonatomic, strong) NSString *name; // 名字 + (instancetype)appWithDict:(NSDictionary *)dict; @end

TDApp.m

#import "TDApp.h"

@implementation TDApp

+ (instancetype)appWithDict:(NSDictionary *)dict {
TDApp *app = [[self alloc] init];
[app setValuesForKeysWithDictionary:dict];
return app;
} @end

6.3 多图下载 - SDWebImage

SDWebImage:

  • iOS中著名的网络图片处理框架

  • 包含的功能:图片下载、图片缓存、下载进度监听、gif处理等等

  • 框架地址:https://github.com/rs/SDWebImage

  • SDWebImage的图片缓存周期是:1周


代码示例:

ViewController.m

#import "TDApp.h"
#import "UIImageView+WebCache.h"
#import "ViewController.h" @interface ViewController ()
@property(nonatomic, strong) NSArray *apps; //所有数据
@property(nonatomic, strong) NSMutableDictionary *imageCache; //内存缓存的图片
@property(nonatomic, strong) NSOperationQueue *queue; //队列对象
@property(nonatomic, strong) NSMutableDictionary *operations; //所有的操作对象 @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} #pragma mark - 数据源方法
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return self.apps.count;
} #pragma mark - Cell
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 重用标识
static NSString *ID = @"app";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; TDApp *app = self.apps[indexPath.row]; #pragma mark - app 名称
cell.textLabel.text = app.name; #pragma mark - 下载量
cell.detailTextLabel.text = app.download; #pragma mark - 图片
// expectedSize: 图片的总字节数 receivedSize: 已经接收的图片字节数
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon]
placeholderImage:[UIImage imageNamed:@"placeholder"]
options:0 // 0 表示什么都不做
progress:^(NSInteger receivedSize, NSInteger expectedSize) { NSLog(@"下载进度:%f", 1.0 * receivedSize / expectedSize);
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType,
NSURL *imageURL) {
NSLog(@"下载完图片");
}];
return cell;
} #pragma mark - 数据懒加载
- (NSArray *)apps {
if (!_apps) {
NSArray *dictArray =
[NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:@"apps.plist"
ofType:nil]]; NSMutableArray *appArray = [NSMutableArray array];
for (NSDictionary *dict in dictArray) {
[appArray addObject:[TDApp appWithDict:dict]];
}
_apps = appArray;
}
return _apps;
} #pragma mark - 懒加载
- (NSMutableDictionary *)imageCache {
if (!_imageCache) {
_imageCache = [NSMutableDictionary dictionary];
}
return _imageCache;
} #pragma mark - 懒加载
- (NSOperationQueue *)queue {
if (!_queue) {
_queue = [[NSOperationQueue alloc] init];
_queue.maxConcurrentOperationCount = 3;
}
return _queue;
} #pragma mark - 懒加载
- (NSMutableDictionary *)operations {
if (!_operations) {
_operations = [NSMutableDictionary dictionary];
}
return _operations;
} #pragma mark - 设置控制器的内存警告
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; self.imageCache = nil;
self.operations = nil;
[self.queue cancelAllOperations];
} @end

TDApp.h

#import <Foundation/Foundation.h>

@interface TDApp : NSObject

@property(nonatomic, strong) NSString *icon;     // 图片
@property(nonatomic, strong) NSString *download; //下载量
@property(nonatomic, strong) NSString *name; // 名字 + (instancetype)appWithDict:(NSDictionary *)dict; @end

TDApp.m

#import "TDApp.h"

@implementation TDApp

+ (instancetype)appWithDict:(NSDictionary *)dict {
TDApp *app = [[self alloc] init];
[app setValuesForKeysWithDictionary:dict];
return app;
} @end

7.0【区别】GCD & NSOperationQueue 队列类型的创建方式

GCD 队列类型的创建方式:

(1)并发队列:手动创建、全局

(2)串行队列:手动创建、主队列


NSOperationQueue的队列类型的创建方法:

(1)主队列:[NSOperationQueue mainQueue]

  • 凡是添加到主队列中的任务(NSOperation),都会放到主线程中执行

(2)其他队列(同时包含了串行、并发功能):[NSOperationQueue alloc]init]

  • 添加到这种队列中的任务(NSOperation),就会自动放到子线程中执行

注:关于SDWebImage框架的详解会另外再写博客

如果你觉得本篇文章对你有所帮助,请点击文章末尾右下角“推荐”,^_^

作者:蓝田(Loto)


出处:http://www.cnblogs.com/shorfng/


如有疑问,请在下方 评论区回复 OR 发送邮件shorfng@126.com联系我。




本文版权归作者和本网站共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

4.4 多线程进阶篇<下>(NSOperation)的更多相关文章

  1. Python开发【第七篇】:面向对象 和 python面向对象进阶篇(下)

    Python开发[第七篇]:面向对象   详见:<Python之路[第五篇]:面向对象及相关> python 面向对象(进阶篇)   上一篇<Python 面向对象(初级篇)> ...

  2. 4.1/4.2 多线程进阶篇<上>(Pthread & NSThread)

    本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末 如果觉得本文内容过长,请前往本人 “简书” 本文源码 Demo 详见 Githubhttps://github.com/shorfng ...

  3. 4.3 多线程进阶篇<中>(GCD)

    更正:队列名称的作用的图中,箭头标注的有些问题,已修正 本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末 如果觉得本文内容过长,请前往本人 “简书” 本文源码 Demo 详见 Gith ...

  4. 多线程下NSOperation、NSBlockOperation、NSInvocationOperation、NSOperationQueue的使用

    本篇文章主要介绍下多线程下NSOperation.NSBlockOperation.NSInvocationOperation.NSOperationQueue的使用,列举几个简单的例子. 默认情况下 ...

  5. iOS开发多线程篇—自定义NSOperation

    iOS开发多线程篇—自定义NSOperation 一.实现一个简单的tableView显示效果 实现效果展示: 代码示例(使用以前在主控制器中进行业务处理的方式) 1.新建一个项目,让控制器继承自UI ...

  6. 【iOS开发】多线程下NSOperation、NSBlockOperation、NSInvocationOperation、NSOperationQueue的使用

    http://blog.csdn.net/crycheng/article/details/21799611 本篇文章主要介绍下多线程下NSOperation.NSBlockOperation.NSI ...

  7. form表单那点事儿(下) 进阶篇

    form表单那点事儿(下) 进阶篇 上一篇主要温习了一下form表单的属性和表单元素,这一片主要讲解用JavaScript如何操作form. 目录: 表单操作 取值 赋值 重置 校验 提交 技巧 不提 ...

  8. Java进阶篇(六)——Swing程序设计(下)

    三.布局管理器 Swing中,每个组件在容器中都有一个具体的位置和大小,在容器中摆放各自组件时很难判断其具体位置和大小,这里我们就要引入布局管理器了,它提供了基本的布局功能,可以有效的处理整个窗体的布 ...

  9. 函数形参为基类数组,实参为继承类数组,下存在的问题------c++程序设计原理与实践(进阶篇)

    示例: #include<iostream> using namespace std; class A { public: int a; int b; A(int aa=1, int bb ...

随机推荐

  1. 静态界面传值javascript

    一:JavaScript静态页面值传递之URL篇能过URL进行传值.把要传递的信息接在URL上.Post.htm 复制代码代码如下: <input type="text" n ...

  2. rewrite

    http://www.iis.net/downloads/microsoft/url-rewrite

  3. web加密的基本概念

    1.需求 了解web加密的一些基础概念. 2.基本概念 a.对称加密方式 对称加密方式 加密和解密用同一个密钥 不足之处是,交易双方都使用同样钥匙,安全性得不到保证.此外,每对用户每次使用对称加密算法 ...

  4. [原创]JavaEE在CentOS服务器上的部署

    1.安装rz.sz命令 yum install lrzsz 2.配置环境变量 vi .bash_profile 默认的path:PATH=$PATH:$HOME/.local/bin:$HOME/bi ...

  5. 没有我的A协

    我离开A协(北京林业大学ACM爱好者协会)有段时间了,严格算来,应该有4年了.现在协会里的大部分人我都不认识.A协在我离开之后的这段时间里也产生了翻天覆地的变化. A协已经不只是一个以竞赛培训为目的的 ...

  6. DispatcherServlet 和 ContextLoaderListener 的关系,到底用哪个?

    我们先看下这两个东东的配置方法: 对于contextConfigLocation参数,有2个地方可以配置: 1)context-param 是全局性配置 2)servlet下的init-param 是 ...

  7. navicat 破解

    首先上官网上下载LINUX版本: http://www.navicat.com/download 下载 navicat110_mysql_en.tar.gz 文件 下载后解压tar文件 tar -zx ...

  8. ffmpeg 常用命令

    mp4中的h264编码,而h264有两种封装: 一种是annexb模式,传统模式,有startcode,SPS和PPS是在ES中:另一种是mp4模式,一般mp4.mkv.avi会没有startcode ...

  9. css实现容器垂直水平居中的七中方法

    方法一:position加margin 方法二: diaplay:table-cell 方法三:position加 transform 方法四:flex;align-items: center;jus ...

  10. user agent stylesheet (浏览器默认样式)!

    "-webkit-margin-before". "-webkit-margin-after". "-webkit-margin-start" ...