Something About NSOperation

一、NSOperation


1.简介
NSOperation 实例封装了需要执行的操作和执行操作所需的数据,并且能够以并发或非并发的方式执行这个操作。NSOperationNSOperationQueue 实现多线程的具体步骤:

  • 先将需要执行的操作封装到一个 NSOperation 对象中
  • 然后将 NSOperation 对象添加到 NSOperationQueue
  • 系统会⾃动将NSOperationQueue 中的 NSOperation 取出来
  • 将取出的 NSOperation 封装的操作放到⼀条新线程中执⾏

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

  • NSInvocationOperation (Foundation框架)
  • NSBlockOperation (Foundation框架)
  • 自定义子类继承 NSOperation ,实现内部相应的⽅法
//创建操作对象,封装要执行的 test 任务
//NSInvocationOperation 封装操作
 NSInvocationOperation *operation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
 [operation start];

【注意】默认情况下,如果操作没有放到队列中queue中,都是同步执行。只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作

//能够并发地执行一个或多个 block 对象,所有相关的 block 都执行完之后,操作才算完成
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){  
        NSLog(@"执行了一个新的操作,线程:%@", [NSThread currentThread]);  
}];  
//通过 addExecutionBlock 方法添加一个 block 操作
[operation addExecutionBlock:^() {  
    NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);  
}];  
 // 开始执行任务(这里还是同步执行)  
[operation start];  

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

3.执行操作
NSOperation 对象的 isConcurrent 方法默认返回 NO,表示操作与调用线程同步执行,start/cancel 开始或取消线程。监听操作的执行,可以通过调用 NSOperation 的 setCompletionBlock 方法来设置想做的事情

operation.completionBlock = ^() {...}; //或者
[operation setCompletionBlock:^() {...}];   

二、自定义 NSOperation

【非常感谢】http://blog.csdn.net/q199109106q/article/details/8565923


1.简介
如果NSInvocationOperationNSBlockOperation 对象不能满足需求, 你可以直接继承 NSOperation , 并添加任何你想要的行为。继承所需的工作量主要取决于你要实现非并发还是并发的 NSOperation 。

  • 定义非并发的 NSOperation 要简单许多,只需要重载-(void)main这个方法,在这个方法里面执行主任务,并正确地响应取消事件
  • 对于并发 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


NSOperationQueue 的作⽤:
NSOperation 可以调⽤ start⽅法来执⾏任务,但默认是同步执行的,如果将NSOperation 添加到 NSOperationQueue (操作队列)中,系统会自动异步执行 NSOperation 中的操作,添加操作到 NSOperationQueue 中,自动执行操作,自动开启线程

//1. 创建一个队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

//2. 添加一个operation
// 第一种方法
[queue addOperation:operation];  
// 第二种方法
[queue addOperationWithBlock:^() {  
    NSLog(@"执行一个新的操作,线程:%@", [NSThread currentThread]);  
}];  

//3. 添加一组operation
[queue addOperations:operations waitUntilFinished:NO]; 

NSOperation添加到queue之后,通常短时间内就会得到运行。但是如果存在依赖,或者整个queue被暂停等原因,也可能需要等待。

【注意】NSOperation添加到queue之后,绝对不要再修改NSOperation对象的状态。因为NSOperation对象可能会在任何时候运行,因此改变NSOperation对象的依赖或数据会产生不利的影响。你只能查看NSOperation对象的状态, 比如是否正在运行、等待运行、已经完成等。


四、内容补充


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

[operation2 addDependency:operation1];

By the way, 优先级不能替代依赖关系,优先级只是对已经准备好的 operations 确定执行顺序。先满足依赖关系,然后再根据优先级从所有准备好的操作中选择优先级最高的那个执行。

2、 设置队列的最大并发操作数量:
setMaxConcurrentOperationCount:方法可以配置queue的最大并发操作数量。设为1就表示queue每次只能执行一个操作。不过operation执行的顺序仍然依赖于其它因素,比如operation是否准备好和operation的优先级等。因此串行化的operation queue并不等同于 GCD 中的串行 dispatch queue

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

3、 等待及暂停 operation :
如果需要在当前线程中处理 operation 完成后的结果,可以使用 NSOperation 的 waitUntilFinished 方法阻塞当前线程,等待 operation 完成。

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

暂停一个queue不会导致正在执行的operation在任务中途暂停,只是简单地阻止调度新Operation执行。

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

最后,NSOperationQueue属于多线程并发处理部分,它主要用来提供一个可添加的操作队列,将一系列操作添加到队列中,然后根据操作的优先级和内部操作依赖决定操作执行的顺序。高优先级的操作先于低优先级执行。一个操作所依赖的操作全部执行完毕后才能执行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容