iOS多线程编程Part 2/3 - NSOperation
多线程编程Part 1介绍了NSThread以及NSRunLoop,这篇Blog介绍另一种并发编程技术:NSOPeration。
NSOperation & NSOperationQueue
从头文件NSOperation.h来看接口是非常的简洁,NSOperation本身是一个抽象类,定义了一个要执行的工作,NSOperationQueue是一个工作队列,当工作加入到队列后,NSOperationQueue会自动按照优先顺序及工作的从属依赖关系(如果有的话)组织执行。
NSOperation是没法直接使用的,它只是提供了一个工作的基本逻辑,具体实现还是需要你通过定义自己的NSOperation子类来获得。如果有必要也可以不将NSOperation加入到一个NSOperationQueue中去执行,直接调用起-start
也可以直接执行。
在继承NSOpertaion后,对于非并发的工作,只需要实现NSOperation子类的main方法:
1 |
|
由于NSOperation的工作是可以取消Cancel的,那么你在main方法处理工作时就需要不断轮询[self isCancelled]
确认当前的工作是否被取消了。
如果要支持并发工作,那么NSOperation子类需要至少override这四个方法:
- start
- isConcurrent
- isExecuting
- isFinished
实现了一个基于Operation的下载器,在Sample Code中可以下载。
1 |
|
start方法是工作的入口,通常是你用来设置线程或者其他执行工作任务需要的运行环境的,注意不要调用[super start];isConcurrent是标识这个Operation是否是并发执行的,这里曾经是个坑,如果你没有实现isConcurrent,默认是返回NO,那么你的NSOperation就不是并发执行而是串行执行的,不过在iOS5.0和OS X10.6之后,已经会默认忽略这个返回值,最终和Queue的maxConcurrentOperationCount最大并发操作值相关;isExecuting和isFinished是用来报告当前的工作执行状态情况的,注意必须是线程访问安全的。
注意你的实现要发出合适的KVO通知,因为如果你的NSOperation实现需要用到工作依赖从属特性,而你的实现里没有发出合适的“isFinished”KVO通知,依赖你的NSOperation就无法正常执行。NSOperation支持KVO的属性有:
- isCancelled
- isConcurrent
- isExecuting
- isFinished
- isReady
- dependencies
- queuePriority
- completionBlock
当然也不是说所有的KVO通知都需要自己去实现,例如通常你用不到addObserver到你工作的“isCancelled”属性,你只需要直接调用cancel方法就可以取消这个工作任务。
实现NSOperation子类后,可以直接调用start或者添加到一个NSOperationQueue里:
1 |
|
NSOperation和NSOperationQueue其他特性
工作是有优先级的,可以通过NSOperation的一下两个接口读取或者设置:
1 |
|
工作之间也可有从属依赖关系,只有依赖的工作完成后才会执行:
1 |
|
还可以通过下面接口设置运行NSOpration的子线程优先级:
1 |
|
如果要设置Queue的并发操作数:
1 |
|
iOS4之后还可以往NSOperation上添加一个结束block,用于在工作执行结束之后的操作:
1 |
|
如果需要阻塞等待NSOperation工作结束(别在主线程这么干),可以使用接口:
1 |
|
NSOperationQueue除了添加NSOperation外,也支持直接添加一个Block(iOS4之后):
1 |
|
NSOperationQueue可以取消所有添加的工作:
1 |
|
也可以阻塞式的等待所有工作结束(别在主线程这么干):
1 |
|
在NSOperation对象中获得被添加的NSOperationQueue队列:
1 |
|
要获得一个绑定在主线程的NSOperationQueue队列:
1 |
|
还有些接口参考头文件NSOperation.h和NSOperation Class Reference,Apple的Class Reference文档描述还是很清晰的。
NSInvocationOperation & NSBlockOperation
其实除非必要,简单的工作完全可以使用官方提供的NSOperation两个子类NSInvocationOperation和NSBlockOperation来实现。
NSInvocationOperation:
1 |
|
NSBlockOperation:
1 |
|
接口非常简单,一看便会。
Sample Code
本文例子放在Github上(工程NSURLConnectionExample中的PTOperationDownloader)。
参考资料
前言
1.上一讲简单介绍了NSThread的使用,虽然也可以实现多线程编程,但是需要我们去管理线程的生命周期,还要考虑线程同步、加锁问题,造成一些性能上的开销。我们也可以配合使用NSOperation和NSOperationQueue实现多线程编程,实现步骤大致是这样的:
1> 先将需要执行的操作封装到一个NSOperation对象中
2> 然后将NSOperation对象添加到NSOperationQueue中
3> 系统会自动将NSOperation中封装的操作放到一条新线程中执行
在此过程中,我们根本不用考虑线程的生命周期、同步、加锁等问题
下面列举一个应用场景,比如微博的粉丝列表:
每一行的头像肯定要从新浪服务器下载图片后才能显示的,而且是需要异步下载。这时候你就可以把每一行的图片下载操作封装到一个NSOperation对象中,上面有6行,所以要创建6个NSOperation对象,然后添加到NSOperationQueue中,分别下载不同的图片,下载完毕后,回到对应的行将图片显示出来。
2.默认情况下,NSOperation并不具备封装操作的能力,必须使用它的子类,使用NSOperation子类的方式有3种:
1> NSInvocationOperation
2> NSBlockOperation
3> 自定义子类继承NSOperation,实现内部相应的方法
这讲先介绍如何用NSOperation封装一个操作,后面再结合NSOperationQueue来使用。
一、NSInvocationOperation
1 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"mj"] autorelease];
2 [operation start];
* 第1行初始化了一个NSInvocationOperation对象,它是基于一个对象和selector来创建操作
* 第2行调用了start方法,紧接着会马上执行封装好的操作,也就是会调用self的run:方法,并且将@"mj"作为方法参数
* 这里要注意:默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作。只有将operation放到一个NSOperationQueue中,才会异步执行操作。
二、NSBlockOperation
1.同步执行一个操作
1 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
2 NSLog(@"执行了一个新的操作");
3 }];
4 // 开始执行任务
5 [operation start];
* 第1行初始化了一个NSBlockOperation对象,它是用一个Block来封装需要执行的操作
* 第2行调用了start方法,紧接着会马上执行Block中的内容
* 这里还是在当前线程同步执行操作,并没有异步执行
2.并发执行多个操作
1 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
2 NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]);
3 }];
4
5 [operation addExecutionBlock:^() {
6 NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);
7 }];
8
9 [operation addExecutionBlock:^() {
10 NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);
11 }];
12
13 [operation addExecutionBlock:^() {
14 NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);
15 }];
16
17 // 开始执行任务
18 [operation start];
* 第1行初始化了一个NSBlockOperation对象
* 分别在第5、9、13行通过addExecutionBlock:方法添加了新的操作,包括第1行的操作,一共封装了4个操作
* 在第18行调用start方法后,就会并发地执行这4个操作,也就是会在不同线程中执行
1 2013-02-02 21:38:46.102 thread[4602:c07] 又执行了1个新的操作,线程:<NSThread: 0x7121d50>{name = (null), num = 1}
2 2013-02-02 21:38:46.102 thread[4602:3f03] 又执行了1个新的操作,线程:<NSThread: 0x742e1d0>{name = (null), num = 5}
3 2013-02-02 21:38:46.102 thread[4602:1b03] 执行第1次操作,线程:<NSThread: 0x742de50>{name = (null), num = 3}
4 2013-02-02 21:38:46.102 thread[4602:1303] 又执行了1个新的操作,线程:<NSThread: 0x7157bf0>{name = (null), num = 4}
可以看出,每个操作所在线程的num值都不一样,说明是不同线程
三、NSOperation的其他用法
1.取消操作
operation开始执行之后, 默认会一直执行操作直到完成,我们也可以调用cancel方法中途取消操作
[operation cancel];
2.在操作完成后做一些事情
如果想在一个NSOperation执行完毕后做一些事情,就调用NSOperation的setCompletionBlock方法来设置想做的事情
operation.completionBlock = ^() {
NSLog(@"执行完毕");
};
当operation封装的操作执行完毕后,就会回调Block里面的内容
四、自定义NSOperation
如果NSInvocationOperation和NSBlockOperation不能满足需求,我们可以直接新建子类继承NSOperation,并添加任何需要执行的操作。如果只是简单地自定义NSOperation,只需要重载-(void)main这个方法,在这个方法里面添加需要执行的操作。
下面写个子类DownloadOperation来下载图片
1.继承NSOperation,重写main方法
DownloadOperation.h
#import <Foundation/Foundation.h>
@protocol DownloadOperationDelegate; @interface DownloadOperation : NSOperation
// 图片的url路径
@property (nonatomic, copy) NSString *imageUrl;
// 代理
@property (nonatomic, assign) id<DownloadOperationDelegate> delegate; - (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate;
@end // 图片下载的协议
@protocol DownloadOperationDelegate <NSObject>
- (void)downloadFinishWithImage:(UIImage *)image;
@end
DownloadOperation.m
1 #import "DownloadOperation.h"
2
3 @implementation DownloadOperation
4 @synthesize delegate = _delegate;
5 @synthesize imageUrl = _imageUrl;
6
7 // 初始化
8 - (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate {
9 if (self = [super init]) {
10 self.imageUrl = url;
11 self.delegate = delegate;
12 }
13 return self;
14 }
15 // 释放内存
16 - (void)dealloc {
17 [super dealloc];
18 [_imageUrl release];
19 }
20
21 // 执行主任务
22 - (void)main {
23 // 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池
24 @autoreleasepool {
25 // ....
26 }
27 }
28 @end
* 在第22行重载了main方法,等会就把下载图片的代码写到这个方法中
* 如果这个DownloadOperation是在异步线程中执行操作,也就是说main方法在异步线程调用,那么将无法访问主线程的自动释放池,所以在第24行创建了一个属于当前线程的自动释放池
2.正确响应取消事件
* 默认情况下,一个NSOperation开始执行之后,会一直执行任务到结束,就比如上面的DownloadOperation,默认会执行完main方法中的所有代码。
* NSOperation提供了一个cancel方法,可以取消当前的操作。
* 如果是自定义NSOperation的话,需要手动处理这个取消事件。比如,一旦调用了cancel方法,应该马上终止main方法的执行,并及时回收一些资源。
* 处理取消事件的具体做法是:在main方法中定期地调用isCancelled方法检测操作是否已经被取消,也就是说是否调用了cancel方法,如果返回YES,表示已取消,则立即让main方法返回。
* 以下地方可能需要调用isCancelled方法:
- 在执行任何实际的工作之前,也就是在main方法的开头。因为取消可能发生在任何时候,甚至在operation执行之前。
- 执行了一段耗时的操作之后也需要检测操作是否已经被取消
1 - (void)main {
2 // 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池
3 @autoreleasepool {
4 if (self.isCancelled) return;
5
6 // 获取图片数据
7 NSURL *url = [NSURL URLWithString:self.imageUrl];
8 NSData *imageData = [NSData dataWithContentsOfURL:url];
9
10 if (self.isCancelled) {
11 url = nil;
12 imageData = nil;
13 return;
14 }
15
16 // 初始化图片
17 UIImage *image = [UIImage imageWithData:imageData];
18
19 if (self.isCancelled) {
20 image = nil;
21 return;
22 }
23
24 if ([self.delegate respondsToSelector:@selector(downloadFinishWithImage:)]) {
25 // 把图片数据传回到主线程
26 [(NSObject *)self.delegate performSelectorOnMainThread:@selector(downloadFinishWithImage:) withObject:image waitUntilDone:NO];
27 }
28 }
29 }
* 在第4行main方法的开头就先判断operation有没有被取消。如果被取消了,那就没有必要往下执行了
* 经过第8行下载图片后,在第10行也需要判断操作有没有被取消
* 总之,执行了一段比较耗时的操作之后,都需要判断操作有没有被取消
* 图片下载完毕后,在第26行将图片数据传递给了代理(delegate)对象
iOS多线程编程Part 2/3 - NSOperation的更多相关文章
- iOS多线程编程指南
iOS多线程编程指南(拓展篇)(1) 一.Cocoa 在Cocoa上面使用多线程的指南包括以下这些: (1)不可改变的对象一般是线程安全的.一旦你创建了它们,你可以把这些对象在线程间安全的传递.另一方 ...
- iOS多线程编程原理及实践
摘要:iOS开发中,开发者不仅要做好iOS的内存管理,而且如果你的iOS涉及多线程,那你也必须了解iOS编程中对多线程的限制,iOS主线程的堆栈大小为1M,其它线程均为512KB,且这个限制开发者是无 ...
- iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD
原文出处: 容芳志的博客 简介iOS有三种多线程编程的技术,分别是:(一)NSThread(二)Cocoa NSOperation(三)GCD(全称:Grand Central Dispatch) 这 ...
- iOS多线程编程--NSOperation(转)
这篇文章写得非常不错,基础用法都涉及到了,我把文章提到的例子都写到了demo里面, 原文地址: iOS多线程--彻底学会多线程之『NSOperation』 demo下载:https://github. ...
- IOS高级编程之三:IOS 多线程编程
多线程的概念在各个操作系统上都会接触到,windows.Linux.mac os等等这些常用的操作系统,都支持多线程的概念. 当然ios中也不例外,但是线程的运行节点可能是我们平常不太注意的. 例如: ...
- iOS多线程编程
废话不多说,直接上干货.先熟悉一下基本知识,然后讲一下常用的两种,NSOperation和GCD. 一.基础概念 进程: 狭义定义:进程是正在运行的程序的实例(an instance of a com ...
- iOS多线程编程(四)------ GCD(Grand Central Dispatch)
一.简单介绍 是基于C语言开发的一套多线程开发机制.也是眼下苹果官方推荐的多线程开发方法.用起来也最简单.仅仅是它基于C语言开发,并不像NSOperation是面向对象的开发.而是全然面向过程的.假设 ...
- iOS多线程编程的知识梳理
多线程编程也称之为并发编程,由于其作用大,有比较多的理论知识,因此在面试中也是受到面试官的青睐.在日常项目开发中,至少网络请求上是需要使用到多线程知识的,虽然使用第三方的框架比如AFNetworkin ...
- iOS多线程编程Part 1/3 - NSThread & Run Loop
前言 多线程的价值无需赘述,对于App性能和用户体验都有着至关重要的意义,在iOS开发中,Apple提供了不同的技术支持多线程编程,除了跨平台的pthread之外,还提供了NSThread.NSOpe ...
随机推荐
- CF Destroying Roads (最短路)
Destroying Roads time limit per test 2 seconds memory limit per test 256 megabytes input standard in ...
- hdu 4711 动态规划
思路:其实这题是个挺水的动态规划,一开始就能AC,可是不知道错哪了,瞎改瞎交,WA了数十次.AC之后怎么改都是AC,也不知道改了什么地方,郁闷死了~~~难道开始时的测试数据有问题??? dp[i][j ...
- poj 1695 动态规划
思路:和黑书上的跳舞机类似 #include<map> #include<set> #include<cmath> #include<queue> #i ...
- 十二、Android UI开发专题(转)
http://dev.10086.cn/cmdn/bbs/viewthread.php?tid=18736&page=1#pid89255Android UI开发专题(一) 之界面设计 近期很 ...
- Java之公约、公倍数
约数和倍数:若整数a能够被b整除,a叫做b的倍数,b就叫做a的约数.公约数:几个数公有的约数,叫做这几个数的公约数:其中最大的一个,叫做这几个数的最大公约数. 那么,我们用编程来看公约数和公倍数: p ...
- mongodb c++ 驱动库编译
git clone 'https://github.com/mongodb/mongo-cxx-driver.git' scons -j2 --c++11=on --sharedclient --us ...
- 苹果在Xcode6中弃用segue的push和model
苹果在Xcode6中弃用了segue的push和model.被show和present取代. 下面是新版本中每种类型的使用总结和例子.建议自己使用前先在测试项目中自己试一试 Show 根据当前屏幕中的 ...
- Linux 内存布局
本文主要简介在X86体系结构下和在ARM体系结构下,Linux内存布局的概况,力求简单明了,不过多深入概念,多以图示的方式来记忆理解,一图胜万言. Technorati 标签: 内存 布局 ...
- cicm0804吊起
CICS调用方法:cicslterm -r RGCIPS /////////////// CECI LINK P(CICM0804) COMM(F0000000000000000000006) --- ...
- 《自动共享LDAP用户并且访问其家目录》RHEL6
实验的目的: 实现ldap服务器上的ldap用户被客户端访问,自动挂载到客户端,并且可以访问ldap用户的家目录. 服务端: 1.只需要配置文件: Iptables –F 关闭selinu ...