iOS多线程之NSOperation

NSOperation是Apple基于GCD的封装,面向对象,抽象层次更高,使用简单。我们只需根据任务类型创建合适的NSOperation,配置好相应的参数,丢到NSOperationQueue中,剩下的工作就交给系统调度处理,我们无需关心。是不是很诱人?

本篇文章主要内容:

  • NSOperation和NSOperationQueue简介
  • **创建操作对象的三种方式 **
  • 设置操作的优先级
  • 设置操作的依赖
  • 获取操作的状态
  • 取消操作
  • 暂停和恢复操作队列
  • 操作完成的回调

NSOperation和NSOperationQueue简介

NSOperation和NSOperationQueue理解起来很容易,我们可以参照GCD相关内容:NSOperation相当于GCD中的任务块,而NSOperationQueue相当于GCD中的并发队列。使用NSOperation编程用到的类主要有NSOperationNSBlockOperationNSInvocationOperationNSOperationQueue。NSBlockOperation和NSInvocationOperation是NSOperation的子类,我们不会使用NSOperation这个抽象基类,而是使用NSBlockOperation和NSInvocationOperation创建我们的任务。下面的内容会具体介绍这些类的使用。代码地址

创建操作对象的三种方式

1.NSInvocationOperation

初始化方法:

initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;

代码示例:

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(sayHello) object:nil];
- (void)sayHello
{
    NSLog(@"Hello World");
}

如果我们创建了一个NSInvocationOperation对象,不放入队列中执行,而是直接调用start方法会是怎样的呢?

NSInvocationOperation *downloadOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadPic) object:nil];
    [downloadOperation start];
    NSLog(@"complete");
- (void)downloadPic
{
    NSLog(@"start download");
    [NSThread sleepForTimeInterval:2.0];
}

运行后发现:打印 "start download" 后,过了2秒才打印 "complete",可以看出没有放入队列而直接调用start运行的操作,会阻塞当前线程,是同步的,只有添加到队列后,操作才会异步执行。下面的NSBlockOperation同理。

2.NSBlockOperation

初始化方法:

blockOperationWithBlock:(void (^)(void))block;

添加操作:

addExecutionBlock:(void (^)(void))block;

代码示例:

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"start download ------ %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.0];
    }];
    
    [blockOperation addExecutionBlock:^{
        NSLog(@"hello1 ------ %@", [NSThread currentThread]);
    }];
    
    [blockOperation addExecutionBlock:^{
        NSLog(@"hello2 ------ %@", [NSThread currentThread]);
    }];
    
    [blockOperation addExecutionBlock:^{
        NSLog(@"hello3 ------ %@", [NSThread currentThread]);
    }];
    
    [blockOperation start];
    
    NSLog(@"complete------ %@", [NSThread currentThread]);

从运行结果可以看出,通过blockOperationWithBlock创建的操作永远在主线程执行,addExecutionBlock添加的其他操作会分发到子线程执行。

3.继承自NSOperation

有些情况下,前面两种方式不能满足我们的需求,就需要自定义NSOperation了。我们可以创建两种类型的NSOperation:非并发的和并发的。本人能力有限,恐介绍不全,想要彻底吃透这块需要一定时间,建议大家直接参照Apple提供的官方文档及示例代码

设置操作的优先级

有时候我们想提前或推迟一些操作的执行,就可以通过设置操作的优先级来达到目的。操作的优先级分为以下几个级别:

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

我们需要注意的是:操作的优先级是相对于同一个操作队列中的其他操作而言的,不同操作队列中的操作的优先级没有可比性。例如:操作队列1中的操作A的优先级为NSOperationQueuePriorityVeryHigh,操作队列2中的操作B的优先级为NSOperationQueuePriorityVeryLow,至于A和B谁先执行,完全不确定。

代码示例:

NSBlockOperation *downloadPicOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"start download picture ------ %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.0];//模拟下载操作
    }];
    [downloadPicOperation setQueuePriority:NSOperationQueuePriorityVeryLow];
    
    
    NSBlockOperation *downloadMusicOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"start download music ------ %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:4.0];//模拟下载操作
    }];
    [downloadMusicOperation setQueuePriority:NSOperationQueuePriorityVeryHigh];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:downloadPicOperation];
    [queue addOperation:downloadMusicOperation];

多次运行代码,我们发现优先级高的不一定会早于优先级低的任务执行,这就是我们需要注意的另外一点:优先级高的操作先执行的概率大,但并不表示必然先执行。设置优先级的代码要在操作放入操作队列之前,否则是不起作用的或者说会产生不利的影响。

设置操作的依赖

在并发编程中,如果任务之间没有执行的先后关系,那么它们并发执行是没问题的,但还有一种情况是某个任务的执行需要其他任务的执行结果,这时候就要通过设置操作的依赖来达到目的,类似于GCD的同步。

代码示例:

- (void)startAction
{
    NSBlockOperation *downloadPicOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"start download picture ------ %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.0];//模拟下载操作
    }];
    
    NSBlockOperation *prepareOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"prepare download picture ------ %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:1.0];//模拟下载前的准备工作
    }];
    
    [downloadPicOperation addDependency:prepareOperation];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:downloadPicOperation];
    [queue addOperation:prepareOperation];
}

如代码所示,我们对下载图片操作添加了依赖,无论运行多少次,下载操作都是等到准备操作执行完成后才执行的。当然,对操作添加依赖也可以发生在不同队列中,需要注意的是不能形成循环依赖关系,会导致死锁。设置依赖的代码要在操作放入操作队列之前,否则是不起作用的或者说会产生不利的影响。与添加依赖对应的操作是移除依赖:

- (void)removeDependency:(NSOperation *)op;

获取操作的状态

操作的生命周期可以表示为:ready —> excute —> finish ,我们将操作放入操作队列后,操作的isReady状态取决于其依赖是否都已执行完成,操作的isExecuting状态表示操作正在执行,操作的isFinished状态表示操作已经执行完成或者被cancel掉了。操作同一时刻只可能是以上三种状态中的一种。

取消操作

取消单个操作,我们可以调用cancel方法;取消操作队列中的所有操作,我们可以调用cancelAllOperations方法。我们最好是在确定不需要某个操作的时候才取消它,因为一旦取消,这个操作就被作为finished处理。

暂停和恢复操作队列

暂停操作队列:

[queue setSuspended:YES];

恢复操作队列:

[queue setSuspended:NO];

当我们暂停某个操作队列时,操作队列就会停止调度新的操作执行,而正在执行的操作不会被停止。

操作完成的回调

代码示例:

- (void)startAction
{
    NSBlockOperation *downloadPicOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"start download picture ------ %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.0];//模拟下载操作
    }];
    
    [downloadPicOperation setCompletionBlock:^{
        NSLog(@"download picture complete");
    }];
    
    NSBlockOperation *downloadMusicOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"start download music ------ %@", [NSThread currentThread]);
        [NSThread sleepForTimeInterval:4.0];//模拟下载操作
    }];
    
    [downloadMusicOperation setCompletionBlock:^{
         NSLog(@"download music complete");
    }];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:downloadPicOperation];
    [queue addOperation:downloadMusicOperation];
}

操作完成的回调可以用于通知主线程任务完成,注意:如果在回调中更新UI,需要派发到主线程执行。

至此,iOS多线程之NSOperation相关内容就总结完了,加上之前的两篇:iOS多线程之GCD、iOS多线程之pthread和NSThread,iOS并发编程基本就覆盖全面了。学以致用,希望自己在以后的实践中不断地总结提升。

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

推荐阅读更多精彩内容