iOS - NSOperation

  1. NSOperation 简介
  1. NSOperation 使用
    1. NSOperation 属性 和 方法
      1.1 属性
      1.2 方法
    2. NSOperation 子类
      2.1 NSInvocationOperation
      属性
      方法
      栗子
      2.2 NSBlockOperation
      属性
      方法
      栗子
    3. 自定义NSOperation
      使用
  2. NSOperationQueue
    3.1 属性和方法
    3.2 创建队列
    3.3 NSOperatinoQueue 串行执行和并行执行 最大并发数
    3.4 操作依赖
    3.5 线程通信

1. NSOperation 简介

NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更简单易用、代码可读性也更高。
是OC语言中基于GCD(GCD详解)的面向对象的封装,具有以下特性

  • 使用起来比GCD更加简单(面向对象);
  • 提供了一些用GCD不好实现的功能,比如可以取消在任务处理队列中的任务,添加任务间的依赖关系等等;
  • 苹果推荐使用,使用NSOperation不用关心线程以及线程的生命周期;
  • 可以指定操作之间的依赖关系,是将操作添加到队列。
  • 并发队列,异步执行(多个线程,无序执行)。

2. NSOperation 使用

NSOperation是个抽象类,并不能封装任务。我们只有使用它的子类来封装任务,先说下 NSOperation类的属性和方法

1. NSOperation 属性和方法

1.1 属性:

注意:
1.取消任务和暂停任务一样, 不会取消当前正在执行的任务, 只能取消还未执行的任务
2.如果在任务执行的过程中暂停队列中的任务, 那么当前正在执行的任务并不会被暂停, 而是会暂停队列中的下一个任务
3.恢复任务, 是从队列第一个没有被执行过的任务开始恢复
    /** 是否已经取消线程 */
    @property (readonly, getter=isCancelled) BOOL cancelled;
    /** 是否正在执行 */
    @property (readonly, getter=isExecuting) BOOL executing;
      /** 是否已经完成 */
    @property (readonly, getter=isFinished) BOOL finished;
      /** 是否并行操作 */
    @property (readonly, getter=isConcurrent) BOOL concurrent;
      /** 是否异步操作 */
    @property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);
      /** 是否能准备运行,这个值和任务的依赖关系相关 */
    @property (readonly, getter=isReady) BOOL ready;
      /** 得到所有依赖的NSOperation任务 */
    @property (readonly, copy) NSArray<NSOperation *> *dependencies;

      /** 该操作的优先级 */
   @property NSOperationQueuePriority queuePriority;

      /** 操作优先级 */
    /*
    typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
        NSOperationQueuePriorityVeryLow = -8L,
        NSOperationQueuePriorityLow = -4L,
        NSOperationQueuePriorityNormal = 0,
        NSOperationQueuePriorityHigh = 4,
        NSOperationQueuePriorityVeryHigh = 8
    };
    */
    /** NSOperation执行完毕后调用 */
    @property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);

1.2 方法:

    /** 开始方法.在当前任务状态和依赖关系合适的情况下,启动NSOperation的main方法任务,需要注意缺省实现只是在当前线程运行。如果需要并发执行,子类必须重写这个方法,并且使 - (BOOL)isConcurrent 方法返回YES */
    - (void)start;
    /** 主方法 start执行后,主类应该重写此方法相比与start方法 */
    - (void)main;
    /** 取消 */
    - (void)cancel;
    /** 添加一个依赖 */ //依赖只是设置先后执行的顺序关系,可以跨队列依赖,不可以循环依赖。添加依赖以后会顺序执行任务,但是不一定开一个线程,可能会开多个线程,但是不会太多
    - (void)addDependency:(NSOperation *)op;
    /** 删除一个依赖 依赖的任务关系不会自动消除,必须调用该方法 */
    - (void)removeDependency:(NSOperation *)op;
    /** 阻塞当前线程,直到该NSOperation结束。可用于线程执行顺序的同步 */
    - (void)waitUntilFinished  //

2. NSOperation 子类

2.1 NSInvocationOperation
属性:

    /** 队列的invocation参数 (只读) */
    @property (readonly, retain) NSInvocation *invocation;
    /** 结果(只读) */
    @property (nullable, readonly, retain) id result;

方法:

   /** 通过SEL实例方法 */
    - (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
    /** 通过NSInvocation 实例方法 */
    - (instancetype)initWithInvocation:(NSInvocation *)inv NS_DESIGNATED_INITIALIZER;
//注意: 1. 调用方法可以使用start也可以放到队列中(NSOperationQueue),但是两者不可同时使用.
//     2. 调用start 开启任务,会在当前线程开启任务,如果几个操作一起调用start 则在当前线程串行完成任务
//     3. 把操作放到队列queue中开启任务会在其他线程去执行任务

栗子:

  //创建
 NSInvocationOperation *skyOpA = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(skyRun:) object:nil];
//开始执行操作(这样是在主线程中,只有加到NSOperationQueue才能并发执行)
 [skyOpA start];

 - (void)skyRun:(NSOperation*)operation
   {
        NSLog(@"skyOpA = %@ ",[NSThread currentThread]);
   }

2.2 NSBlockOperation
属性:

    /** 执行的所有blocks */
    @property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;

实例方法和类方法:

  /** 添加blocks 没有参数没有返回值 */
  - (void)addExecutionBlock:(void (^)(void))block;
  /** 通过block初始化操作,该block没有参数没有返回值 */
  + (instancetype)blockOperationWithBlock:(void (^)(void))block;

栗子:

    NSBlockOperation *skyOpB = [NSBlockOperation blockOperationWithBlock:^{
        //在主线程
        NSLog(@"skyOpB 1 = %@",[NSThread currentThread]);
    }];
    
    //添加操作 以下都在多线程中~
    [skyOpB addExecutionBlock:^{
        NSLog(@"skyOpB 2 = %@",[NSThread currentThread]);
    }];
    
    [skyOpB addExecutionBlock:^{
        NSLog(@"skyOpB 3 = %@",[NSThread currentThread]);
    }];
    
    [skyOpB addExecutionBlock:^{
        NSLog(@"skyOpB 4 = %@",[NSThread currentThread]);
    }];
    // 如果只封装了一个操作, 那么默认会在主线程中执行
    // 如果封装了多个操作, 那么除了第一个操作以外, 其它的操作会在子线程中执行
    [skyOpB start];

2.3 自定义 ** NSOperation**
先定义一个继承自NSOperation的子类,重写main方法
栗子:

#import <Foundation/Foundation.h>
   @interface SkyOperation : NSOperation
   @end

  #import "SkyOperation.h"
   @interface SkyOperation()
   @property(assign,nonatomic,getter= isExecuting)BOOL executing;
   @property(assign,nonatomic,getter= isFinished)BOOL  finished;
   @end 

   @implementation SkyOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
    - (void)main
{
      NSLog(@" skyOp = %@ ",[NSThread currentThread]);
      //    @autoreleasepool {
      //    }
}
    - (void)setFinished:(BOOL)finished
{
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}
   - (void)setExecuting:(BOOL)executing
{
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}
@end

使用自定义的NSOperation

   SkyOperation *operation = [[SkyOperation alloc]init];
   [operation start];

注意:

  • 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
  • 经常通过-(BOOL)isCancelled方法检测操作是否被取消,对取消做出响应
  • 关于NSOperation的子类在NSOperationQueue中执行完无法释放的问题你的NSOperation dealloc了么?并发数真的生效了么?

3. NSOperationQueue

NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作

3.1 属性和方法

属性:

 /** 返回已加入执行operation的数组,当某个operation结束后会自动从这个数组清除 */
 @property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
 /** 当前队列的操作数量 */
 @property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
 /** 最大并发量 (默认是-1,最大为6) */
 @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);

方法:

    /** 添加操作 */
    - (void)addOperation:(NSOperation *)op;
    /** 批量参加操作 wait标志是否当前线程等待所有operation结束后,才返回 */
    - (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);
    /** 取消所有操作 取消所有operation的执行,实质是调用各个operation的cancel方法 */
    - (void)cancelAllOperations;
    /** 当前线程等待,直到opA和opB都执行结束 opA opB都是 添加到队列中的NSOperation */
    - (void)waitUntilAllOperationsAreFinished;

#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
    /** 返回当前NSOperationQueue,如果当前线程不是在NSOperationQueue上运行则返回nil */
    @property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0);
    /** 返回主线程的NSOperationQueue,缺省总是有一个queue */
    @property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0);
#endif

3.2 创建队列

- (void)addOperationToQueue
{
    //创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //添加到队列(自动调用start)
    [queue addOperationWithBlock:^{
        NSLog(@"queue 1 %@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"queue 2 %@",[NSThread currentThread]);
    }];
    
    //NSInvocationOperation
    NSInvocationOperation *skyOpA = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(skyRun:) object:nil];
    
    //NSBlockOperation
    NSBlockOperation *skyOpB = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"skyOpB 1 = %@",[NSThread currentThread]);
    }];
    
    //SkyOperation
    SkyOperation *operation = [[SkyOperation alloc]init];
    
    [queue addOperation:skyOpA];//自动调用start
    [queue addOperation:skyOpB];//自动调用start
    [queue addOperation:operation];
    
}


- (void)skyRun:(NSOperation*)operation
{
    NSLog(@"skyOpA = %@ ",[NSThread currentThread]);
}

3.3 NSOperatinoQueue 串行执行和并行执行 最大并发数

@property NSInteger maxConcurrentOperationCount;
  • maxConcurrentOperationCount 默认等于 -1, 代表不限制, 可以创建N多线程
  • alloc/init的NSOperatinoQueue队列默认就是并发, 如果想实现串行, 那么就设置maxConcurrentOperationCount = 1
  • 注意: 最大并发数, 不能设置为0, 否则任务不会被执行
    //    queue.maxConcurrentOperationCount = 2; //最大并发数
    queue.maxConcurrentOperationCount = 1; // 就变成了串行队列

3.4 操作依赖

操作A执行完后,才能执行操作B. 注意: 不能相互依赖

//操作依赖
- (void)addDependency
{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *opA = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"A-----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *opB = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"B-----%@", [NSThread  currentThread]);
    }];
    
    [opB addDependency:opA];    // 让opB 依赖于 opA, 执行完opA再执行opB
    
    [queue addOperation:opA];
    [queue addOperation:opB];
}

3.5 线程通信

开启线程下载图片,下载完成之后主线程显示图片

   //开启线程
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperationWithBlock:^{
        
        //异步下载图片
        
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
        
            //主线程显示图片
            
        }];
    }];

如果书写有误 望指出 ~

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

推荐阅读更多精彩内容