多线程编程Part 1介绍了NSThread以及NSRunLoop,这篇Blog介绍另一种并发编程技术:NSOPeration。

NSOperation & NSOperationQueue

从头文件NSOperation.h来看接口是非常的简洁,NSOperation本身是一个抽象类,定义了一个要执行的工作,NSOperationQueue是一个工作队列,当工作加入到队列后,NSOperationQueue会自动按照优先顺序及工作的从属依赖关系(如果有的话)组织执行。

NSOperation是没法直接使用的,它只是提供了一个工作的基本逻辑,具体实现还是需要你通过定义自己的NSOperation子类来获得。如果有必要也可以不将NSOperation加入到一个NSOperationQueue中去执行,直接调用起-start也可以直接执行。

在继承NSOpertaion后,对于非并发的工作,只需要实现NSOperation子类的main方法:

1
2
3
4
5
6
7
8
9
10
11
-(void)main
{
@try
{
// 处理工作任务
}
@catch(...)
{
// 处理异常,但是不能再重新抛出异常
}
}

由于NSOperation的工作是可以取消Cancel的,那么你在main方法处理工作时就需要不断轮询[self isCancelled]确认当前的工作是否被取消了。

如果要支持并发工作,那么NSOperation子类需要至少override这四个方法:

  • start
  • isConcurrent
  • isExecuting
  • isFinished

实现了一个基于Operation的下载器,在Sample Code中可以下载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
- (void)operationDidStart
{
[self.lock lock];
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:self.URL
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:self.timeoutInterval];
[request setHTTPMethod: @"GET"];
self.connection =[[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:NO];
[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.connection start];
[self.lock unlock];
}
- (void)operationDidFinish
{
[self.lock lock];
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
self.executing = NO;
self.finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
[self.lock unlock];
}
- (void)start
{
[self.lock lock];
if ([self isCancelled])
{
[self willChangeValueForKey:@"isFinished"];
self.finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
[self willChangeValueForKey:@"isExecuting"];
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkThread] withObject:nil waitUntilDone:NO];
self.executing = YES;
[self didChangeValueForKey:@"isExecuting"];
[self.lock unlock];
}
- (void)cancel
{
[self.lock lock];
[super cancel];
if (self.connection)
{
[self.connection cancel];
self.connection = nil;
}
[self.lock unlock];
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return self.executing;
}
- (BOOL)isFinished {
return self.finished;
}

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
2
3
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:downloader];

NSOperation和NSOperationQueue其他特性

工作是有优先级的,可以通过NSOperation的一下两个接口读取或者设置:

1
2
- (NSOperationQueuePriority)queuePriority;
- (void)setQueuePriority:(NSOperationQueuePriority)p;

工作之间也可有从属依赖关系,只有依赖的工作完成后才会执行:

1
2
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;

还可以通过下面接口设置运行NSOpration的子线程优先级:

1
- (void)setQueuePriority:(NSOperationQueuePriority)priority;

如果要设置Queue的并发操作数:

1
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

iOS4之后还可以往NSOperation上添加一个结束block,用于在工作执行结束之后的操作:

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

如果需要阻塞等待NSOperation工作结束(别在主线程这么干),可以使用接口:

1
- (void)waitUntilFinished;

NSOperationQueue除了添加NSOperation外,也支持直接添加一个Block(iOS4之后):

1
- (void)addOperationWithBlock:(void (^)(void))block

NSOperationQueue可以取消所有添加的工作:

1
- (void)cancelAllOperations;

也可以阻塞式的等待所有工作结束(别在主线程这么干):

1
- (void)waitUntilAllOperationsAreFinished;

在NSOperation对象中获得被添加的NSOperationQueue队列:

1
+ (id)currentQueue

要获得一个绑定在主线程的NSOperationQueue队列:

1
+ (id)mainQueue

还有些接口参考头文件NSOperation.h和NSOperation Class Reference,Apple的Class Reference文档描述还是很清晰的。

NSInvocationOperation & NSBlockOperation

其实除非必要,简单的工作完全可以使用官方提供的NSOperation两个子类NSInvocationOperation和NSBlockOperation来实现。

NSInvocationOperation:

1
2
3
4
NSInvocationOperation* theOp = [[NSInvocationOperation alloc]
initWithTarget:self
selector:@selector(myTaskMethod:)
object:data];

NSBlockOperation:

1
2
3
4
NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{
NSLog(@"Beginning operation.\n");
// Do some work.
}];

接口非常简单,一看便会。

Sample Code

本文例子放在Github上(工程NSURLConnectionExample中的PTOperationDownloader)。

参考资料

Concurrency Programming Guide

NSOperation Class Reference

前言

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的更多相关文章

  1. iOS多线程编程指南

    iOS多线程编程指南(拓展篇)(1) 一.Cocoa 在Cocoa上面使用多线程的指南包括以下这些: (1)不可改变的对象一般是线程安全的.一旦你创建了它们,你可以把这些对象在线程间安全的传递.另一方 ...

  2. iOS多线程编程原理及实践

    摘要:iOS开发中,开发者不仅要做好iOS的内存管理,而且如果你的iOS涉及多线程,那你也必须了解iOS编程中对多线程的限制,iOS主线程的堆栈大小为1M,其它线程均为512KB,且这个限制开发者是无 ...

  3. iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD

    原文出处: 容芳志的博客 简介iOS有三种多线程编程的技术,分别是:(一)NSThread(二)Cocoa NSOperation(三)GCD(全称:Grand Central Dispatch) 这 ...

  4. iOS多线程编程--NSOperation(转)

    这篇文章写得非常不错,基础用法都涉及到了,我把文章提到的例子都写到了demo里面, 原文地址: iOS多线程--彻底学会多线程之『NSOperation』 demo下载:https://github. ...

  5. IOS高级编程之三:IOS 多线程编程

    多线程的概念在各个操作系统上都会接触到,windows.Linux.mac os等等这些常用的操作系统,都支持多线程的概念. 当然ios中也不例外,但是线程的运行节点可能是我们平常不太注意的. 例如: ...

  6. iOS多线程编程

    废话不多说,直接上干货.先熟悉一下基本知识,然后讲一下常用的两种,NSOperation和GCD. 一.基础概念 进程: 狭义定义:进程是正在运行的程序的实例(an instance of a com ...

  7. iOS多线程编程(四)------ GCD(Grand Central Dispatch)

    一.简单介绍 是基于C语言开发的一套多线程开发机制.也是眼下苹果官方推荐的多线程开发方法.用起来也最简单.仅仅是它基于C语言开发,并不像NSOperation是面向对象的开发.而是全然面向过程的.假设 ...

  8. iOS多线程编程的知识梳理

    多线程编程也称之为并发编程,由于其作用大,有比较多的理论知识,因此在面试中也是受到面试官的青睐.在日常项目开发中,至少网络请求上是需要使用到多线程知识的,虽然使用第三方的框架比如AFNetworkin ...

  9. iOS多线程编程Part 1/3 - NSThread & Run Loop

    前言 多线程的价值无需赘述,对于App性能和用户体验都有着至关重要的意义,在iOS开发中,Apple提供了不同的技术支持多线程编程,除了跨平台的pthread之外,还提供了NSThread.NSOpe ...

随机推荐

  1. Codeforces Round #197 (Div. 2) C,D两题

    开了个小号做,C题一开始看错范围,D题看了半小时才看懂,居然也升到了div1,囧. C - Xenia and Weights 给出一串字符串,第i位如果是1的话,表示有重量为i的砝码,如果有该种砝码 ...

  2. Eclipse+ADT+Android SDK 搭建安卓开发环境

    Eclipse+ADT+Android SDK 搭建安卓开发环境   要求 必备知识 windows 7 基本操作. 运行环境 windows 7(64位); eclipse-jee-luna-SR2 ...

  3. C# 新特性 dynamic的使用及扩展

    个人而言感觉C#的dynamic是一个特别实用的东西,为日常开发工作中的封装,数据传递等带来了很高的可扩展性. C#4.0中通过对数据类型后期绑定的支持,演化出了dynamic.任何直接声明为这种类型 ...

  4. ASP.NET缓存全解析2:页面输出缓存 转自网络原文作者李天平

    页面输出缓存是最为简单的缓存机制,该机制将整个ASP.NET页面内容保存在服务器内存中.当用户请求该页面时,系统从内存中输出相关数据,直到缓存数据过期.在这个过程中,缓存内容直接发送给用户,而不必再次 ...

  5. SQL server自定义函数实例

    create function dbo.t_bh (@str varchar(20)) returns varchar(20) as begin declare @bh varchar(20),@le ...

  6. Game start

    今天开始有计划的码代码吧!!我可是以后要进微软或者google的男人.初步计划先学习编程之美吧,每天码一到题的解法,每天每天每天..然后是ACM竞赛基础,每天一节同上.最后..不对,冷静冷静,我已经没 ...

  7. 零基础Android学习笔记-01 安卓开发环境搭建

    安卓开发环境搭建. 1.首先准备JDK,从官网找到JDK下载地址,原来做.NET不熟悉JAVA,干脆用最新的,下载了JDK 1.7的版本.原来装过1.5还要配置环境变量什么的.但1.7好像很给力,装好 ...

  8. 苹果系统开发中的混合编程(2):Swift和C的相互调用

      在进行Swift和C之间的相互调用时,有必要先了解一下两种语言之间的类型转换关系:   C 类型 Swift 类型 bool CBool char, signed char CChar unsig ...

  9. (转)MongoDB分片实战 集群搭建

    环境准备 Linux环境 主机 OS 备注 192.168.32.13 CentOS6.3 64位 普通PC 192.168.71.43 CentOS6.2 64位 服务器,NUMA CPU架构 Mo ...

  10. CSS制作彩虹效果

    今天看到一篇文章,说到margin的塌陷的问题,并提供了好几个例子. 自己之前还没怎么遇到过这个问题,正好来研究一下. <div class="box1"></d ...