OC--多线程NSOperation

前言

NSOperation相比GCD的好处有:

1、NSOperation 可以指定操作间的依赖关系。
2、NSOperation 可以通过KVO提供对NSOperation对象的精细控制。
3、NSOperation 可以指定操作优先级。
4、NSOperation 可以自定义子类实现操作重用。
5、NSOperationQueue 可以调用cancel方法来取消某个操作。

NSOperation

直接上代码NSOperation.h

@interface NSOperation : NSObject {

- (void)start;//启动任务 默认加入到当前队列
- (void)main; //自定义NSOperation,写一个子类,重写这个方法,在这个方法里面添加需要执行的操作。

@property (readonly, getter=isCancelled) BOOL cancelled;//是否已经取消,只读
- (void)cancel;//取消任务
@property (readonly, getter=isExecuting) BOOL executing;//正在执行,只读
@property (readonly, getter=isFinished) BOOL finished;//执行结束,只读
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);//是否并发,只读
@property (readonly, getter=isReady) BOOL ready;//准备执行

- (void)addDependency:(NSOperation *)op;//添加依赖
- (void)removeDependency:(NSOperation *)op;//移除依赖
//所有依赖关系,只读
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
//系统提供的优先级关系枚举
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};
//执行优先级
@property NSOperationQueuePriority queuePriority;
//任务执行完成之后的回调
@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);
//阻塞当前线程,等到operation执行完毕。
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);
//已废弃,用qualityOfService替代。
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);

//服务质量,一个高质量的服务就意味着更多的资源得以提供来更快的完成操作。
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
//任务名称
@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);

@end

三种创建方法

1、NSBlockOperation
2、NSInvocationOperation
3、NSOperation自定义子类

NSBlockOperation
    NSLog(@"currentThread=====%@",[NSThread currentThread]);
    
    //1.封装操作
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        //当前线程中执行
        NSLog(@"任务1--%@",[NSThread currentThread]);
    }];
    
    // 2.1 追加操作,追加的操作在子线程中执行,必须在start前面
    // 2.2 但最大并发数为几个(这与dispatch_apply差不多)(包括主线程在内)
    [operation addExecutionBlock:^{
        NSLog(@"任务2--%@",[NSThread currentThread]);
    }];
    
    //3.启动执行操作
    [operation start];

    /*
     NSLog输出信息
     currentThread=====<NSThread: 0x60000026e400>{number = 3, name = (null)}
     任务1--<NSThread: 0x60000026e400>{number = 3, name = (null)}
     任务2--<NSThread: 0x608000266ec0>{number = 4, name = (null)}
     */
NSInvocationOperation
    NSLog(@"currentThread=====%@",[NSThread currentThread]);
    //1.封装操作
    /*
     第一个参数:目标对象
     第二个参数:该操作要调用的方法,最多接受一个参数
     第三个参数:调用方法传递的参数,如果方法不接受参数,那么该值传nil
     */
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]
                                        initWithTarget:self selector:@selector(run) object:nil];
    
    //2.启动操作
    [operation start];//开始调用run方法,在当前线程执行任务
    /*
     NSLog输出信息
     currentThread=====<NSThread: 0x6000000765c0>{number = 1, name = main}
     run=====<NSThread: 0x6000000765c0>{number = 1, name = main}
     */
NSOperation自定义子类

NSOperation有两个方法:main() 和 start()。
1、如果想使用同步,那么最简单方法的就是把逻辑写在main()中,
2、如果想使用异步,需要把逻辑写到start()中,然后加入到队列之中。

    //自定义的NSOperation,通过重写内部的main方法实现封装操作
    @implementation CustomOperation
    - (void)main {
       //2秒延时
        [NSThread sleepForTimeInterval:2];
        NSLog(@"CustomOperation main===%@",[NSThread   currentThread]);
    }
    //使用如下
    CustomOperation *operation = [CustomOperation new];
    [operation start];
    // 同步start,执行完operation的main,operation也dealloc了

使用异步过程中还需要重载一些状态方法(可参考SDWebImageDownloaderOperation),如下

@implementation CustomOperation

@synthesize executing = _executing;
@synthesize finished = _finished;

- (void)start {
    // 就绪开始
    [self setExecuting:YES];
    // 2秒延时
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        NSLog(@"=====%@",@"2秒后任务2完成");
        [self setExecuting:NO];
        [self setFinished:YES];
    });
}

#pragma mark - 需要重载的状态方法
- (void)setFinished:(BOOL)finished {
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}

- (void)setExecuting:(BOOL)executing {
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

使用例子

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 任务1
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务1====%@",[NSThread currentThread]);
    }];
    
    CustomOperation *operation2 = [[CustomOperation new] init];

    // 设置任务1依赖任务2,等待任务2完成
    [operation1 addDependency:operation2];
    
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    /*
     NSLog输出信息
     =====2秒后任务2完成
     任务1====<NSThread: 0x60800007f3c0>{number = 4, name = (null)}
     */
各种状态
@property (readonly, getter=isCancelled) BOOL cancelled;//是否已经取消,只读
- (void)cancel;//取消任务
@property (readonly, getter=isExecuting) BOOL executing;//正在执行,只读
@property (readonly, getter=isFinished) BOOL finished;//执行结束,只读
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);//是否并发,只读
@property (readonly, getter=isReady) BOOL ready;//准备执行
458529-0d86e44aeae2c0d2.png

线程start后并不是立即执行,而是进入一个就绪的状态(isReady),由系统调度执行。
当这几个状态值改变时需要使用KVO通知,其中处于Pending、Ready跟Executing状态的operation是可以被cancel的,而当operation处于finished状态是无法被取消的。当operation成功结束、失败或者被取消了,isFinished的值都会被设置为yes,所以不能仅仅靠isFinished==YES认为operation成功执行。

NSOperation任务执行完成之后的回调completionBlock
operation.completionBlock = ^{
   NSLog(@"完成");
};
任务的执行顺序

任务的执行顺序,以下情况都有影响:
1、任务与任务依赖关系:

[operationA addDependency:operationB]; 
//可以在不同queue的NSOperation之间创建依赖关系
//不要相互依赖,如A依赖B,B依赖A,死锁
// 初始化三个块操作
NSBlockOperation *op1 =[NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下载 %@", [NSThread currentThread]);
}];

NSBlockOperation *op2 =[NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"美化 %@", [NSThread currentThread]);
}];

NSBlockOperation *op3 =[NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"更新 %@", [NSThread currentThread]);
}];

// 通过添加依赖可以控制线程执行顺序,依赖关系可以多重依赖
// 注意:不要建立循环依赖,会造成死锁
[op2 addDependency:op1];
[op3 addDependency:op2];

// 直接加到队列里面会并发执行
[self.queue addOperation:op3];
[self.queue addOperation:op1];
[self.queue addOperation:op2];

2、任务的优先级

typedef NS_ENUM(NSInteger, NSQualityOfService) {
// 与用户交互的任务,这些任务通常跟UI级别的刷新相关,比如动画,这些任务需要在一瞬间完成
NSQualityOfServiceUserInteractive = 0x21,
// 在实现用户精确请求请求相关工作时使用UserInitiated QoS,但不要求精确到毫秒,比如动画。例如,如果用户打开email app马上查看邮件。
NSQualityOfServiceUserInitiated = 0x19,
// Utility QoS用于执行已经由用户请求自动发生的任务。例如,电子邮件应用程序可以被配置为每隔5分钟自动检查邮件。如果系统是非常有限的资源,而电子邮件检查被推迟几分钟这也是被允许的。
NSQualityOfServiceUtility = 0x11,
// Background QoS用于执行用户可能甚至都没有意识到正在发生的工作,比如email app可能使用它来执行索引搜索
NSQualityOfServiceBackground = 0x09,
// 优先级介于user-initiated 和 utility,当没有 QoS信息时默认使用,开发者不应该使用这个值来设置自己的任务
NSQualityOfServiceDefault = -1
} NS_ENUM_AVAILABLE(10_10, 8_0);

NSOperationQueue

@interface NSOperationQueue : NSObject {
- (void)addOperation:(NSOperation *)op;//添加任务
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);//添加一组任务

- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);//添加一个block形式的任务

@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;//队列中所有的任务数组
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);//队列中的任务数

@property NSInteger maxConcurrentOperationCount;//最大并发数

@property (getter=isSuspended) BOOL suspended;//暂停

@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);//名称

@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);//服务质量,一个高质量的服务就意味着更多的资源得以提供来更快的完成操作。

@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0); // 底层对应的GCD队列

- (void)cancelAllOperations;//取消队列中的所有任务

- (void)waitUntilAllOperationsAreFinished;//阻塞当前线程,等到队列中的任务全部执行完毕。

#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0);//获取当前队列
@property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0);//获取主队列
#endif

@end

一般使用

    NSLog(@"test start------%@",[NSThread currentThread]);
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 任务1
    NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    [queue addOperation:invocationOper];
    
    // 任务2
    NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务2------%@",[NSThread currentThread]);
    }];
    [queue addOperation:blockOper];
    
    // 任务3
    [queue addOperationWithBlock:^{
        NSLog(@"QUEUEBlockOperationRun_%@",[NSThread currentThread]);
    }];
    
    
    NSBlockOperation *block4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务4------%@",[NSThread currentThread]);
    }];
    NSBlockOperation *block5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务5------%@",[NSThread currentThread]);
    }];
    
    // 任务4、5
    [queue addOperations:@[block4,block5] waitUntilFinished:NO];
    NSLog(@"test end------%@",[NSThread currentThread]);

    /*
     NSLog输出信息
     test start------<NSThread: 0x60800026fac0>{number = 3, name = (null)}
     test end------<NSThread: 0x60800026fac0>{number = 3, name = (null)}
     QUEUEBlockOperationRun_<NSThread: 0x600000271800>{number = 4, name = (null)}
     任务1------<NSThread: 0x6000002718c0>{number = 5, name = (null)}
     任务2------<NSThread: 0x608000270880>{number = 6, name = (null)}
     任务4------<NSThread: 0x600000271ac0>{number = 7, name = (null)}
     任务5------<NSThread: 0x608000270a00>{number = 8, name = (null)}
     */
队列类型

1、主队列:[NSOperationQueue mainqueue];凡是放在主队列中的操作都在主线程中串行执行
2、非主队列:[[NSOperationQueue alloc]init],并发和串行,默认是并发执行的

最大并发数maxConcurrentOperationCount

1、maxConcurrentOperationCount默认情况下为-1,表示不进行限制,默认为并发执行。
2、maxConcurrentOperationCount大于1时,进行并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整。
3、maxConcurrentOperationCount为0,不执行
4、maxConcurrentOperationCount为1时,进行串行执行,但是线程可能是多条(如下代码测试)

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1; // 就变成了串行队列
    for (int i=0; i< 10; i++) {
        [queue addOperationWithBlock:^{
            NSLog(@"第%zd个-----%@",i,[NSThread currentThread]);
            [NSThread sleepForTimeInterval:0.1];
        }];
    }
    /*
     NSLOG输出信息:看出开了2个新线程,按顺序执行
     第0个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
     第1个-----<NSThread: 0x6000002615c0>{number = 5, name = (null)}
     第2个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
     第3个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
     第4个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
     第5个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
     第6个-----<NSThread: 0x6000002615c0>{number = 5, name = (null)}
     第7个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
     第8个-----<NSThread: 0x6000002615c0>{number = 5, name = (null)}
     第9个-----<NSThread: 0x60000007eb40>{number = 3, name = (null)}
     */
暂停和恢复以及取消
    //设置暂停和恢复
    //suspended设置为YES表示暂停,suspended设置为NO表示恢复
    //暂停表示不继续执行队列中的下一个任务,暂停操作是可以恢复的
    self.queue.suspended = !self.queue.suspended;


    //取消队列里面的所有操作
    //取消之后,当前正在执行的操作的下一个操作将不再执行,而且永远都不在执行,就像后面的所有任务都从队列里面移除了一样
    //取消操作是不可以恢复的
    [self.queue cancelAllOperations];

cancelAllOperations取消队列里面的所有操作
1、移除还没有开始执行的任务,Operation里面还没有执行不需要处理
2、正在执行的任务,Operation的isCancelled为YES,系统不会让我们的Operation自动停止运行代码,但是我们可以监听isCancelled状态,处理自定义Operation里面的逻辑

@implementation CustomOperation
- (void)main {
    NSLog(@"CustomOperation start===%@==isCancelled=%zd",[NSThread currentThread],self.isCancelled);
    //2秒延时
    [NSThread sleepForTimeInterval:2];
    NSLog(@"CustomOperation end===%@==isCancelled=%zd",[NSThread currentThread],self.isCancelled);
}


- (void)test{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 任务1
    [queue addOperation:[CustomOperation new]];
    // 任务2
    [queue addOperationWithBlock:^{
        
        [NSThread sleepForTimeInterval:1];
        [queue cancelAllOperations];
        NSLog(@"CancelAllOperations------%@",[NSThread currentThread]);
    }];

    /*
     NSLog输出信息
     CustomOperation start===<NSThread: 0x608000262300>{number = 4, name = (null)}==isCancelled=0
     CancelAllOperations------<NSThread: 0x600000263780>{number = 5, name = (null)}
     CustomOperation end  ===<NSThread: 0x608000262300>{number = 4, name = (null)}==isCancelled=1
     */
}
等待所有操作完成
[queue waitUntilAllOperationsAreFinished];//阻塞当前线程,等到队列中的任务全部执行完毕。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,423评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,339评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,241评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,503评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,824评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,262评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,615评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,337评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,989评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,300评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,829评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,193评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,753评论 3 230
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,970评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,708评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,295评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,207评论 2 258

推荐阅读更多精彩内容