NSOperation和NSOperationQueue的一些事儿

在面对多线程的时候,大多数会选择NSOperation或者GCD来实现,GCD由于使用起来非常方便,应该是很多开发者的首选,不过你会发现其实很多开源代码都是使用NSOpertaion来执行异步任务,所以这次我们来说说NSOperation跟NSOperationQueue,以及它的强大之处。

NSOPerationQueue

NSOperation可以通过调用start方法同步地执行相应的任务,不过通常NSOprtaion都是配合NSOPerationQueue来使用的,NSOperationQueue可以看作是一种高级的dispatch queue,将NSOperation加入到queue中,queue会自动异步的执行该NSOperation

//创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];  

//将NSOperation加入队列之后,queue会自动执行该operation
[queue addOperation:operation];  

在gcd编程的时候,我们无法取消block对应的任务,不过NSOPerationQueue之所以称为高级的dispatch queue除了能异步的执行任务之外,还能够:

  • 取消操作
    当NSOperation加入到NSOperationQueue之后,可以通过调用cancel方法取消单个任务,如果想取消所有的任务可以调用cancelAllOperations方法;
// 取消一个任务 
[operation cancel];  
  
// 取消queue中所有的任务  
[queue cancelAllOperations]; 
  • 设置最大并发数
    我们可以通过设置maxConcurrentOperationCount来设置队列的最大并发数,比如当网络为Wi-Fi时设置为6,3G时设置为2:
//设置并发数目为2
queue.maxConcurrentOperationCount = 2;  

假如maxConcurrentOperationCount的值设为1,可以看作该队列为串行队列,每次只能执行一个任务。不过NSOPerationQueue不是先进先出(FIFO)队列,这点跟dispatch queue有点区别,dispatch queue中的block会按照FIFO的顺序去执行,NSOPerationQueue会根据Operation的状态(是否Ready)以及优先级来确定执行的NSOperation的顺序。

  • 暂停跟继续
    假如想要暂停NSOperation的执行,可以调用queue的setSuspended:方法暂停queue继续调度运行新的任务,不过正在执行的任务无法暂停
// 暂停队列运行任务
[queue setSuspended:YES];

// 继续
[queue setSuspended:NO];
  • 队列优先级
    iOS8之后引入了QualityOfService的概念,类似于线程的优先级,设置不同的QualityOfService值后系统会分配不同的CPU时间、网络资源和硬盘资源等,因此我们可以通过这个设置队列的优先级,NSQualityOfService定义了几个枚举值:

UserInteractive: 任务跟界面的一些UI相关,比如绘制屏幕内容跟处理点击事件等,处于最高优先级的任务

UserInitiated : 用户一些请求的任务,关系到后面的交互,比如用户点击消息按钮后获取邮件列表的任务

** Utility:** 处理一些用户并不立即需要结果的任务,比如定期的内容更新之类的任务

** Background**:后台任务,用户不会察觉到这些任务,比如后台对文件进行索引方便后续搜索,优先级最低;

** Default:** 默认值,介于UserInitiated跟Utility之间

NSOperaion

NSOperation可以看作是高级的dispatch block(dispatch_block_t),我们可以将一个任务封装成一个NSOperation对象,然后放到NSOperationQueue中去异步执行。NSOperation分为concurrent(并发任务)跟non-concurrent(非并发任务)两种,两者主要是生命周期的管理有些区别,NSOperation是一个抽象类,我们需要继承于它并实现一些方法。(当任务相对简单的时候,可以直接使用NSInvocationOperation或是NSBlockOperation,这两者也都继承自NSOperation)

  • non-concurrent operation
    实现一个non-concurrent operation只需要简单的继承NSOperation并实现main方法即可,NSOperationQueue会调用NSOperation的start方法,然后在start方法中调用main方法:
@interface NonConcurrentOperation : NSOperation
@end
@implementation NonConcurrentOperation

-(void)main
{
    NSLog(@"main called");
    
    dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
    __weak typeof(self) weakSelf = self;
    dispatch_after(time, dispatch_get_main_queue(), ^{
        
        NSLog(@"weakSelf:%@",weakSelf);
    });
}
-(void)dealloc
{
    NSLog(@"dealloc called");
}

@end

non-concurrent operation会同步的运行main方法,不管期间的任务需要运行多长时间,当在执行完main方法后该operation对象会被释放。假如你在main方法中执行了异步的操作时会出现错误,比如上述的代码可以发现main方法运行完后就调用dealloc方法释放了,导致weakSelf的值为空。

NSOperations[1467:91483] main called
NSOperations[1467:91483] dealloc called
NSOperations[1467:91218] weakSelf:(null)
  • concurrent operation
    当你的任务是并发的任务是异步任务时,你需要的是继承NSOperation并至少实现start、isExecuting和isFinished方法,其中isExecuting跟isFinished两个属性值的改变需要做KVO通知。
    concurrent operation的生命周期跟non-concurrent operation不一样,可以在start方法中执行异步的方法,当start方法执行完之后该operation对象不会被释放,直到该operation执行结束,收到isFinished的kvo通知。
//状态枚举
typedef NS_ENUM(NSInteger, ConcurrentOperationState) {
    ConcurrentOperationReadyState = 1,
    ConcurrentOperationExecutingState,
    ConcurrentOperationFinishedState
};

@interface ConcurrentOperation ()
@property (nonatomic, assign) ConcurrentOperationState state;
@end

@implementation ConcurrentOperation

- (BOOL)isReady {
    self.state = ConcurrentOperationReadyState;
    return self.state == ConcurrentOperationReadyState;
}
- (BOOL)isExecuting{
    return self.state == ConcurrentOperationExecutingState;
}
- (BOOL)isFinished{
    return self.state == ConcurrentOperationFinishedState;
}

- (void)start {
    __weak typeof(self) weakSelf = self;
    dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(), ^{

        //kvo:结束
        [weakSelf willChangeValueForKey:@"isFinished"];
        weakSelf.state = ConcurrentOperationFinishedState;
        [weakSelf didChangeValueForKey:@"isFinished"];
        
        NSLog(@"finished :%@",weakSelf);
    });
    NSLog(@"start called");

    //kvo:正在执行
    [weakSelf willChangeValueForKey:@"isExecuting"];
    weakSelf.state = ConcurrentOperationExecutingState;
    [weakSelf didChangeValueForKey:@"isExecuting"];
}

-(void)dealloc{
    NSLog(@"dealloc called");
}

可以从打印的日志看到,当start方法结束后对象并没有立即被释放,只有发出isFinished的kvo通知后,该operation对象才会被释放

NSOperations[1556:103301] start called
NSOperations[1556:103242] finished :<ConcurrentOperation: 0x79a47bf0>
NSOperations[1556:103242] dealloc called
  • NSOperation状态
NSOperation状态

从上面的图可以看到nsoperation的几种状态,当这几个状态值改变时需要使用KVO通知,其中处于Pending、Ready跟Executing状态的operation是可以被cancel的,而当operation处于finished状态是无法被取消的。当operation成功结束、失败或者被取消了,isFinished的值都会被设置为yes,所以不能仅仅靠isFinished==YES认为operation成功执行。

  • 设置优先级
    我们可以设置operation运行的优先级,优先级的选择主要有:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

//设置优先级
[operation1 setQueuePriority:NSOperationQueuePriorityVeryLow]; 

当operation被添加到队列之后,NSOperationQueue会浏览所有的operation,优先运行那些处于ready状态且优先级较高的操作。

  • Dependencies
    NSOperation的另一个强大之处就是可以添加依赖,当operation1依赖于operation2的时候,系统可以保证只有当operation2结束的时候,operation1才会运行,而且依赖不局限于一个队列,你可以依赖一个不同队列的NSOperation。
    
@interface NonConcurrentOperation ()
@property(nonatomic,strong)NSNumber *number;
@end

@implementation NonConcurrentOperation

-(id)initWithNumber:(NSNumber *)number{
    self = [super init];
    if (self) {
        self.number = number;
    }
    return self;
}

-(void)main
{
    NSLog(@"main called, %@",self.number);
}
@end

//测试代码
NSOperationQueue  *queue1 = [NSOperationQueue new];
NSOperationQueue  *queue2 = [NSOperationQueue new];
    
NonConcurrentOperation *op1 = [[NonConcurrentOperation alloc] initWithNumber:@(1)];
NonConcurrentOperation *op2 = [[NonConcurrentOperation alloc] initWithNumber:@(2)];
NonConcurrentOperation *op3 = [[NonConcurrentOperation alloc] initWithNumber:@(3)];
NonConcurrentOperation *op4 = [[NonConcurrentOperation alloc] initWithNumber:@(4)];

//添加依赖
[op1 addDependency:op2];
[op2 addDependency:op3];

//可以依赖不同队列的operation
[op3 addDependency:op4];
    
[queue1 addOperation:op1];
[queue1 addOperation:op2];
[queue1 addOperation:op3];
[queue2 addOperation:op4]; //添加到不同队列中

输出结果:

NSOperations[2105:179596] main called, 4
NSOperations[2105:179596] main called, 3
NSOperations[2105:179596] main called, 2
NSOperations[2105:179596] main called, 1

添加依赖的时候需要注意不要相互依赖:

[op1 addDependency:op2];
[op2 addDependency:op1];

如果相互依赖,双方都会等待对方结束导致相互之间都无法执行。

互相依赖.png
  • 抽象化逻辑
    NSOperation可以用来抽象化一些业务逻辑,业务之间有依赖的情况可以通过dependency来简化。比如有的业务场景需要收藏一篇文章,由于收藏文章需要用户的信息,而获取用户的信息又需要去登陆获取,所以我们可以将登陆、获取用户信息以及收藏文章抽象封装封装成3个operation,而其中的逻辑关系通过dependency来实现,降低逻辑复杂度,而且业务之间的关系变得可组装:
//获取用户信息需要登陆完才能执行
[userInfoOperation addDependency:loginOperation];

//收藏需要获取到用户信息后才能执行
[favorOperation addDependency:userInfoOperation];
业务间的联系.png

不过NSOperation跟NSOperation底层也是基于GCD实现的,它是更高层次的抽象,当你框架设计涉及到这块内容的时候应该优先考虑使用NSOperations,这样框架的结构相对会好些,类似SDwebimgag跟AFNetworking等很多开源框架都在使用NSOperations。不过不可否认gcd代码编写更简便,这边就当抛砖引玉吧~

参考资料

NSOperation
認識NSOperation
Advanced NSOperations

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

推荐阅读更多精彩内容