美文网首页面试题 For BearLiniOSios
NSOperation和NSOperationQueue的一些事

NSOperation和NSOperationQueue的一些事

作者: 树下老男孩 | 来源:发表于2015-11-02 16:21 被阅读4212次

在面对多线程的时候,大多数会选择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

相关文章

网友评论

  • da27c260cc85:想问一下, 你自定的operation, override start method 后, 把这个operation放到queue中, 会执行么? 为什么我的不会执行呢?
  • 叶舞清风:Gcd 可以在block中做一些操作啊
  • 8d5116c71e14:如果登陆,获取用户信息,收藏都是异步的网络请求,操作完成的时间无法确定,作者上面的依赖似乎没有多大用处,如果也不能写在回调里面,如何解决这种问题?
    bigParis:@树下的老男孩 有这种异步请求操作的demo吗?
    树下老男孩:@Ketil 一个concurrent operation是需要你在结束的时候发isfinished的kvo通知之后,依赖的operation才会处于ready状态,才会被执行,比如获取用户信息,当你登陆异步登陆完成之后,你需要发出一个isfinished的kvo通知,这是获取用户信息的operation就会变成ready,然后开始执行。
  • visual_:清晰,感谢分享。

本文标题:NSOperation和NSOperationQueue的一些事

本文链接:https://www.haomeiwen.com/subject/awmfhttx.html