Objective-C的NSOperation多线程类基本使用指南

NSOperation

一、NSOperation
1.简介
NSOperation实例封装了需要执行的操作和执行操作所需的数据,并且能够以并发或非并发的方式执行这个操作。
NSOperation本身是抽象基类,因此必须使用它的子类,使用NSOperation子类的方式有2种:
1> Foundation框架提供了两个具体子类直接供我们使用:NSInvocationOperation和NSBlockOperation
2> 自定义子类继承NSOperation,实现内部相应的方法

2.执行操作
NSOperation调用start方法即可开始执行操作,NSOperation对象默认按同步方式执行,也就是在调用start方法的那个线程中直接执行。NSOperation对象的isConcurrent方法会告诉我们这个操作相对于调用start方法的线程,是同步还是异步执行。isConcurrent方法默认返回NO,表示操作与调用线程同步执行

3.取消操作
operation开始执行之后, 默认会一直执行操作直到完成,我们也可以调用cancel方法中途取消操作

代码如下:

[operation cancel];

4.监听操作的执行
如果我们想在一个NSOperation执行完毕后做一些事情,就调用NSOperation的setCompletionBlock方法来设置想做的事情

代码如下:

operation.completionBlock = ^() { 
    NSLog(@"执行完毕"); 
};

或者

代码如下:

[operation setCompletionBlock:^() { 
    NSLog(@"执行完毕"); 
}];

二、NSInvocationOperation
1.简介
基于一个对象和selector来创建操作。如果你已经有现有的方法来执行需要的任务,就可以使用这个类

2.创建并执行操作

代码如下:

// 这个操作是:调用self的run方法 
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil]; 
// 开始执行任务(同步执行) 
[operation start];

三、NSBlockOperation
1.简介
能够并发地执行一个或多个block对象,所有相关的block都执行完之后,操作才算完成

2.创建并执行操作

代码如下:

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){ 
        NSLog(@"执行了一个新的操作,线程:%@", [NSThread currentThread]); 
}]; 
 // 开始执行任务(这里还是同步执行) 
[operation start];

3.通过addExecutionBlock方法添加block操作

代码如下:

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){ 
    NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]); 
}]; 
 
[operation addExecutionBlock:^() { 
    NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]); 
}]; 
 
[operation addExecutionBlock:^() { 
    NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]); 
}]; 
 
[operation addExecutionBlock:^() { 
    NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]); 
}]; 
 
// 开始执行任务 
[operation start];

打印信息如下:

2013-02-02 21:38:46.102 thread[4602:c07] 又执行了1个新的操作,线程:<NSThread: 0x7121d50>{name = (null), num = 1}
2013-02-02 21:38:46.102 thread[4602:3f03] 又执行了1个新的操作,线程:<NSThread: 0x742e1d0>{name = (null), num = 5}
2013-02-02 21:38:46.102 thread[4602:1b03] 执行第1次操作,线程:<NSThread: 0x742de50>{name = (null), num = 3}
2013-02-02 21:38:46.102 thread[4602:1303] 又执行了1个新的操作,线程:<NSThread: 0x7157bf0>{name = (null), num = 4}

可以看出,这4个block是并发执行的,也就是在不同线程中执行的,num属性可以看成是线程的id

四、自定义NSOperation
1.简介
如果NSInvocationOperation和NSBlockOperation对象不能满足需求, 你可以直接继承NSOperation, 并添加任何你想要的行为。继承所需的工作量主要取决于你要实现非并发还是并发的NSOperation。定义非并发的NSOperation要简单许多,只需要重载-(void)main这个方法,在这个方法里面执行主任务,并正确地响应取消事件; 对于并发NSOperation, 你必须重写NSOperation的多个基本方法进行实现(这里暂时先介绍非并发的NSOperation)

2.非并发的NSOperation
比如叫做DownloadOperation,用来下载图片
1> 继承NSOperation,重写main方法,执行主任务
DownloadOperation.h

代码如下:

#import <Foundation/Foundation.h> 
@protocol DownloadOperationDelegate; 
 
@interface DownloadOperation : NSOperation 
// 图片的url路径 
@property (nonatomic, copy) NSString *imageUrl; 
// 代理 
@property (nonatomic, retain) id<DownloadOperationDelegate> delegate; 
 
- (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate; 
@end

代码如下:

// 图片下载的协议 
@protocol DownloadOperationDelegate <NSObject> 
- (void)downloadFinishWithImage:(UIImage *)image; 
@end

DownloadOperation.m

代码如下:

#import "DownloadOperation.h" 
 
@implementation DownloadOperation 
@synthesize delegate = _delegate; 
@synthesize imageUrl = _imageUrl; 
 
// 初始化 
- (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate { 
    if (self = [super init]) { 
        self.imageUrl = url; 
        self.delegate = delegate; 
    } 
    return self; 

// 释放内存 
- (void)dealloc { 
    [super dealloc]; 
    [_delegate release]; 
    [_imageUrl release]; 

 
// 执行主任务 
- (void)main { 
    // 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池 
    @autoreleasepool { 
        // .... 
    } 

@end

2> 正确响应取消事件
operation开始执行之后,会一直执行任务直到完成,或者显式地取消操作。取消可能发生在任何时候,甚至在operation执行之前。尽管NSOperation提供了一个方法,让应用取消一个操作,但是识别出取消事件则是我们自己的事情。如果operation直接终止, 可能无法回收所有已分配的内存或资源。因此operation对象需要检测取消事件,并优雅地退出执行
NSOperation对象需要定期地调用isCancelled方法检测操作是否已经被取消,如果返回YES(表示已取消),则立即退出执行。不管是自定义NSOperation子类,还是使用系统提供的两个具体子类,都需要支持取消。isCancelled方法本身非常轻量,可以频繁地调用而不产生大的性能损失
以下地方可能需要调用isCancelled:
* 在执行任何实际的工作之前
* 在循环的每次迭代过程中,如果每个迭代相对较长可能需要调用多次
* 代码中相对比较容易中止操作的任何地方
DownloadOperation的main方法实现如下

代码如下:

- (void)main { 
    // 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池 
    @autoreleasepool { 
        if (self.isCancelled) return; 
         
        // 获取图片数据 
        NSURL *url = [NSURL URLWithString:self.imageUrl]; 
        NSData *imageData = [NSData dataWithContentsOfURL:url]; 
         
        if (self.isCancelled) { 
            url = nil; 
            imageData = nil; 
            return; 
        } 
         
        // 初始化图片 
        UIImage *image = [UIImage imageWithData:imageData]; 
         
        if (self.isCancelled) { 
            image = nil; 
            return; 
        } 
         
        if ([self.delegate respondsToSelector:@selector(downloadFinishWithImage:)]) { 
            // 把图片数据传回到主线程 
            [(NSObject *)self.delegate performSelectorOnMainThread:@selector(downloadFinishWithImage:) withObject:image waitUntilDone:NO]; 
        } 
    } 
}

NSOperationQueue
一、简介
一个NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的。也可以将NSOperation添加到一个NSOperationQueue(操作队列)中去执行,而且是异步执行的。
创建一个操作队列:

代码如下:

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

二、添加NSOperation到NSOperationQueue中
1.添加一个operation

代码如下:

[queue addOperation:operation];

2.添加一组operation

代码如下:

[queue addOperations:operations waitUntilFinished:NO];

3.添加一个block形式的operation

代码如下:

[queue addOperationWithBlock:^() { 
    NSLog(@"执行一个新的操作,线程:%@", [NSThread currentThread]); 
}];

NSOperation添加到queue之后,通常短时间内就会得到运行。但是如果存在依赖,或者整个queue被暂停等原因,也可能需要等待。
注意:NSOperation添加到queue之后,绝对不要再修改NSOperation对象的状态。因为NSOperation对象可能会在任何时候运行,因此改变NSOperation对象的依赖或数据会产生不利的影响。你只能查看NSOperation对象的状态, 比如是否正在运行、等待运行、已经完成等

三、添加NSOperation的依赖对象
1.当某个NSOperation对象依赖于其它NSOperation对象的完成时,就可以通过addDependency方法添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前NSOperation对象才会开始执行操作。另外,通过removeDependency方法来删除依赖对象。

代码如下:

[operation2 addDependency:operation1];

依赖关系不局限于相同queue中的NSOperation对象,NSOperation对象会管理自己的依赖, 因此完全可以在不同的queue之间的NSOperation对象创建依赖关系

唯一的限制是不能创建环形依赖,比如A依赖B,B依赖A,这是错误的

2.依赖关系会影响到NSOperation对象在queue中的执行顺序,看下面的例子:
1> 没有设置依赖关系

代码如下:

NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 
 
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^(){ 
    NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]); 
}]; 
 
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){ 
    NSLog(@"执行第2次操作,线程:%@", [NSThread currentThread]); 
}]; 
 
[queue addOperation:operation1]; 
[queue addOperation:operation2];

打印信息:

2013-02-03 00:21:35.024 thread[5616:3d13] 执行第1次操作,线程:<NSThread: 0x7658570>{name = (null), num = 3}
2013-02-03 00:21:35.063 thread[5616:1303] 执行第2次操作,线程:<NSThread: 0x765a2e0>{name = (null), num = 4}

可以看出,默认是按照添加顺序执行的,先执行operation1,再执行operation2

2> 设置了依赖关系

代码如下:

NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 
 
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^(){ 
    NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]); 
}]; 
 
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){ 
    NSLog(@"执行第2次操作,线程:%@", [NSThread currentThread]); 
}]; 
// operation1依赖于operation2 
[operation1 addDependency:operation2]; 
 
[queue addOperation:operation1]; 
[queue addOperation:operation2];

打印信息:

2013-02-03 00:24:16.260 thread[5656:1b03] 执行第2次操作,线程:<NSThread: 0x7634490>{name = (null), num = 3}
2013-02-03 00:24:16.285 thread[5656:1303] 执行第1次操作,线程:<NSThread: 0x9138b50>{name = (null), num = 4}

可以看出,先执行operation2,再执行operation1

四、修改Operations的执行顺序
对于添加到queue中的operations,它们的执行顺序取决于2点:
1.首先看看NSOperation是否已经准备好:是否准备好由对象的依赖关系确定
2.然后再根据所有NSOperation的相对优先级来确定。优先级等级则是operation对象本身的一个属性。默认所有operation都拥有“普通”优先级,不过可以通过setQueuePriority:方法来提升或降低operation对象的优先级。优先级只能应用于相同queue中的operations。如果应用有多个operation queue,每个queue的优先级等级是互相独立的。因此不同queue中的低优先级操作仍然可能比高优先级操作更早执行。
注意:优先级不能替代依赖关系,优先级只是对已经准备好的 operations确定执行顺序。先满足依赖关系,然后再根据优先级从所有准备好的操作中选择优先级最高的那个执行。

五、设置队列的最大并发操作数量
队列的最大并发操作数量,意思是队列中最多同时运行几条线程
虽然NSOperationQueue类设计用于并发执行Operations,你也可以强制单个queue一次只能执行一个Operation。setMaxConcurrentOperationCount:方法可以配置queue的最大并发操作数量。设为1就表示queue每次只能执行一个操作。不过operation执行的顺序仍然依赖于其它因素,比如operation是否准备好和operation的优先级等。因此串行化的operation queue并不等同于GCD中的串行dispatch queue

代码如下:

// 每次只能执行一个操作 
queue.maxConcurrentOperationCount = 1; 
// 或者这样写 
[queue setMaxConcurrentOperationCount:1];

六、取消Operations
一旦添加到operation queue,queue就拥有了这个Operation对象并且不能被删除,唯一能做的事情是取消。你可以调用Operation对象的cancel方法取消单个操作,也可以调用operation queue的cancelAllOperations方法取消当前queue中的所有操作。

代码如下:

// 取消单个操作 
[operation cancel]; 
 
// 取消queue中所有的操作 
[queue cancelAllOperations];

七、等待Options完成
为了最佳的性能,你应该设计你的应用尽可能地异步操作,让应用在Operation正在执行时可以去处理其它事情。如果需要在当前线程中处理operation完成后的结果,可以使用NSOperation的waitUntilFinished方法阻塞当前线程,等待operation完成。通常我们应该避免编写这样的代码,阻塞当前线程可能是一种简便的解决方案,但是它引入了更多的串行代码,限制了整个应用的并发性,同时也降低了用户体验。绝对不要在应用主线程中等待一个Operation,只能在第二或次要线程中等待。阻塞主线程将导致应用无法响应用户事件,应用也将表现为无响应。

代码如下:

// 会阻塞当前线程,等到某个operation执行完毕 
[operation waitUntilFinished];

除了等待单个Operation完成,你也可以同时等待一个queue中的所有操作,使用NSOperationQueue的waitUntilAllOperationsAreFinished方法。注意:在等待一个 queue时,应用的其它线程仍然可以往queue中添加Operation,因此可能会加长线程的等待时间。

代码如下:

// 阻塞当前线程,等待queue的所有操作执行完毕 
[queue waitUntilAllOperationsAreFinished];

八、暂停和继续queue
如果你想临时暂停Operations的执行,可以使用queue的setSuspended:方法暂停queue。不过暂停一个queue不会导致正在执行的operation在任务中途暂停,只是简单地阻止调度新Operation执行。你可以在响应用户请求时,暂停一个queue来暂停等待中的任务。稍后根据用户的请求,可以再次调用setSuspended:方法继续queue中operation的执行

代码如下:

// 暂停queue 
[queue setSuspended:YES]; 
 
// 继续queue 
[queue setSuspended:NO];

(0)

相关推荐

  • 详解Objective-C设计模式编程中对备忘录模式的运用

    基本理解 这个模式有三个关键角色:原发器(Originator).备忘录(Memento).看管人(caretaker).三者的基本关系是:原发器创建一个包含其状态的备忘录,并传给看管人.看管人不知道如何与备忘录交互,但会把备忘录放在一个安全之处保管好. 备忘录(Memento):在 不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可以将该对象回复到原先保存的状态. Originator(发起人):负责创建一个备忘录,用以记录当前时刻它的内部状态,并且可使用恢

  • 以实例讲解Objective-C中的KVO与KVC机制

    KVO实例浅析 最近遇到个问题,在处理项目中一个评论界面时,因为直接用的是UIWebView展示评论列表,结果取到的页面上下都有一段CGSize为(320,65)的乱七八糟的广告,十分碍眼.头部广告因很方便的在头部坐标贴上自己的logo解决了,但是尾部的,因为每个页面的评论长短不一,坐标也就不一样,这样就不能给定死坐标去贴logo,思前想后,通过KVO很好的解决了这个问题. @KVO概述: KVO,即:Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,则对象就会

  • 理解Objective-C的变量以及面相对象的继承特性

    OC点语法和变量作用域 一.点语法 (一)认识点语法 声明一个Person类: 复制代码 代码如下: #import <Foundation/Foundation.h> @interface Person : NSObject {     int _age;//默认为@protected } - (void)setAge:(int)age; - (int)age; @end Person类的实现: 复制代码 代码如下: #import "Person.h" @impleme

  • Objective-C中编程中一些推荐的书写规范小结

    一.类 1. 类名 类名应该以三个大写字母作为前缀(双字母前缀为Apple的类预留) 不仅仅是类,公开的常量.Protocol等的前缀都为相同的三个大写字母. 当你创建一个子类的时候,你应该把说明性的部分放在前缀和父类名的中间. 例如: 如果你有一个 ZOCNetworkClient 类,子类的名字会是ZOCTwitterNetworkClient (注意 "Twitter" 在 "ZOC" 和 "NetworkClient" 之间); 按照这个

  • 详解Objective-C编程中对设计模式中适的配器模式的使用

    引言 在项目开发中,有时候会遇到这样的一种情景:需要使用以前开发的"一些现存的对象",但是新环境中要求的接口是这些现存对象所不满足的.怎样应对这种迁移的需求?使得可以复用这些对象,以满足新的应用环境,这就是适配器(Adapter)所要解决的问题. 定义 "将一个类的接口转换成客户希望的另外一个接口.适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作." 最初的定义出现于<设计模式>(Addison-Wesley,1994). 这个定义应该

  • 简介Objective-C解析XML与JSON数据格式的方法

    解析XML 本文以解析本地XML为例,网络获取到的返回值只需转换成NSData型,解析是同理 需要解析的xml文件如下,users.xml <?xml version="1.0" encoding="UTF-8"?> <AllUsers> <message>用户信息</message> <user> <name>芳仔小脚印</name> <age>10</age&g

  • Objective-C编程中语句和变量的一些编写规范建议

    语句 条件语句 条件语句体应该总被大括号包围.只有一行代码最好也加上,否则会带来安全隐患. 复制代码 代码如下: // 推荐 if (!error) {     return success; } // 不推荐 if (!error)     return success; if (!error) return success; 尤达表达式(Yoda) 不要使用尤达表达式.(名字起源于星球大战中尤达大师的讲话方式,总是用倒装的语序) 复制代码 代码如下: // 推荐 if ([myValue i

  • 简单讲解Objective-C的基本特性及其内存管理方式

    一.OC简介 Oc语言在c语言的基础上,增加了一层最小的面向对象语法,完全兼容C语言,在OC代码中,可以混用c,甚至是c++代码. 可以使用OC开发mac osx平台和ios平台的应用程序. 拓展名:c语言-.c  OC语言.-m  兼容C++.-mm 注:其实c语言和oc甚至任何一门语言都只是我们为了实现一些功能,达到一些效果而采用的工具,抛开语法的差别外,我想最重要的应该是在解决问题的时候考虑的角度和方法不一样而已,然而这也构成了学习一门语言的重要性. 二.语法预览 (1)关键字 基本上所有

  • 浅析Objective-C中分类Category的使用

    无论一个类设计的如何完美,都不可避免的会遇到没有预测到的需求,那怎么扩展现有的类呢?当然,继承是个不错的选择.但是Objective-C提供了一种特别的方式来扩展类,叫Catagory,可以动态的为已经存在的类添加新的行为.这样可以保证类的原原来的基础上,较小的改动就可以增加需要的功能.使用Category对类进行扩展时,不需要访问其源代码,也不需要创建子类,这样我们可以扩展系统提供的类.Category使用简单的方式,实现了类的相关方法的模块化,把不同的类方法分配到不同的分类文件中. 使用Ob

  • 举例讲解Objective-C中@property属性的用法

    学过c/c++的朋友都知道,我们定义struct/class的时候,如果把访问限定符(public,protected,private)设置为public的话,那么我们是可以直接用.号来访问它内部的数据成员的.比如 //in Test.h class Test { public: int i; float f; }; 我在main函数里面是可以通过下面的方式来使用这个类的:(注意,如果在main函数里面使用此类,除了要包含头文件以外,最重要的是记得把main.m改成main.mm,否则会报一些奇

随机推荐