iOS多线程-NSOperation&NSOperationQueue

祭出demo
Foundation框架提供了NSOperation和NSOpeartionQueue 这两个面向对象的多线程类,这两个类与GCD的功能类似。NSOperation是对任务的封装,NSOperationQueue提供执行队列,自动实现多核并行计算,自动管理线程的生命周期。并发情况下也是底层提供线程池模型来管理。但是NSOperation提供了更多的可定制开发。

使用NSOperationNSOperationQueue来编写多线程程序非常简单,只需要创建一个任务对象,创建一个执行队列或者像获取主线程一样获取一个主任务队列,然后将任务提交给队列即可实现并发,如过你想要串行只需要将队列的并发数设置为1即可。接下来将分别介绍两个类的使用。

1、NSOperation任务的封装
这里的任务就是NSOperation类的一个方法,main方法或start方法,但NSOperation类的这两个方法是空方法,没有干任何事情,因此,我们需要自定义继承NSOperation类并重写相关方法,OC也提供了两个子类供我们使用NSBlockOperationNSInvocationOperation

接下来看一下NSOperation类中比较重要的属性和方法:

/*
对于并发Operation需要重写该方法
也可以不把operation加入到队列中,手动触发执行,与调用普通方法一样
*/
- (void)start;

/*
非并发Operation需要重写该方法
*/
- (void)main;

//只读属性任务是否取消,如果自定义子类,需要重写该属性
@property (readonly, getter=isCancelled) BOOL cancelled;

/*
设置cancelled属性为YES
仅仅标记cancelled属性,不退出任务,和NSThread的cancel一个机制
自定义子类时需要使用该属性判断是否在外部触发了取消任务的操作,手动退出任务
*/
- (void)cancel;

//只读属性,任务是否正在执行,如果自定义子类,需要重写该属性
@property (readonly, getter=isExecuting) BOOL executing;

/*
只读属性,任务是否结束,如果自定义子类,需要重写该方法
对于加入到队列的任务来说,当finished设置为YES后,队列会将任务移除出队列
*/
@property (readonly, getter=isFinished) BOOL finished;

//是否为并发任务,该属性已经被标识即将弃用,应该使用下面的asynchronous属性
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below

/*
只读属性,判断任务是否为并发任务,默认返回NO
如果需要自定义并发任务子类,需要重写getter方法返回YES
*/
@property (readonly, getter=isAsynchronous) BOOL asynchronous;

/*
只读属性,任务是否准备就绪
对于加入队列的任务,当ready为YES,标识该任务即将开始执行
如果任务有依赖的任务没有执行完成ready为NO
*/
@property (readonly, getter=isReady) BOOL ready;

/*
添加一个NSOperation为当前任务的依赖
如果一个任务有依赖,需要等待依赖的任务执行完成才能开始执行
*/
- (void)addDependency:(NSOperation *)op;

//删除一个依赖
- (void)removeDependency:(NSOperation *)op;

//任务在队列里的优先级
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

//任务在队列里的优先级
@property NSOperationQueuePriority queuePriority;

/*
任务完成后的回调方法
当finished属性设置为YES时才会执行该回调
*/
@property (nullable, copy) void (^completionBlock)(void);

上述内容中有一些属性和方法是在自定义NSOperation子类中必须要重写的,自定义子类能够提供更高的可定制性,因此,编写自定义子类更复杂。
OC为我们提供了两个子类NSBlockOperationNSInvocationOperation,这两个子类已经帮我们完成了各种属性的设置操作,我们只需要编写一个任务的block或者一个方法即可像使用GCD一样方便的编写多线程程序。

接下来举两个创建任务的例子:


//创建一个NSBlockOperation对象,传入一个block
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 100; i++)
    {
        NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
    }
}];

/*
创建一个NSInvocationOperation对象,指定执行的对象和方法
该方法可以接收一个参数即object
*/
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"Hello, World!"];

2、NSOperationQueue执行队列

看一下该类中有哪些比较重要的属性和方法:

//向队列中添加一个任务
- (void)addOperation:(NSOperation *)op;

/*
向队列中添加一组任务
是否等待任务完成,如果YES,则阻塞当前线程直到所有任务完成
如果为False,不阻塞当前线程
*/
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;

//向队列中添加一个任务,任务以block的形式传入,使用更方便
- (void)addOperationWithBlock:(void (^)(void))block;

//获取队列中的所有任务
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;

//获取队列中的任务数量
@property (readonly) NSUInteger operationCount;

/*
队列支持的最大任务并发数
如果为1,则只支持同时处理一个任务,即串行队列,主队列就是串行队列使用主线程执行任务
如果为大于1的数,则支持同时处理多个任务,即并发队列,底层使用线程池管理多个线程来执行任务
默认是-1(NSOperationQueueDefaultMaxConcurrentOperationCount),表示不对最大并发数做限制
*/
@property NSInteger maxConcurrentOperationCount;

//队列是否挂起
@property (getter=isSuspended) BOOL suspended;

//队列的名称
@property (nullable, copy) NSString *name;

/*
取消队列中的所有任务
即所有任务都执行cancel方法,所有任务的cancelled属性都置为YES
*/
- (void)cancelAllOperations;

//阻塞当前线程直到所有任务完成
- (void)waitUntilAllOperationsAreFinished;

//类属性,获取当前队列
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue;

//类属性,获取主队列,任务添加到主队列就会使用主线程执行,主队列的任务并发数为1,即串行队列
@property (class, readonly, strong) NSOperationQueue *mainQueue;

类属性maxConcurrentOperationCount的值决定了,当前队列是串行还是并发队列。当值是1时,表示串行队列,大于1表示并发队列,当值为NSOperationQueueDefaultMaxConcurrentOperationCount时,表示不对最大任务数做限制。

3、操作数添加依赖

NSOperationQueue *operationQueue = [[NSOperationQueue alloc]init];
    operationQueue.name = @"firstQueue";
    [operationQueue addOperation:invocationOperation];
    
    NSLog(@"queue is %@",NSOperationQueue.currentQueue);
    
    
    NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSInteger i = 0;
        while (i < 10) {
            i++;
            NSLog(@"task3:%ld thread:%@",i,NSThread.currentThread);
        }
    }];
    
    NSBlockOperation *blockOperation4 = [NSBlockOperation blockOperationWithBlock:^{
        NSInteger i = 0;
        while (i < 10) {
            i++;
            NSLog(@"task4:%ld thread:%@",i,NSThread.currentThread);
        }
    }];
    NSBlockOperation *blockOperation5 = [NSBlockOperation blockOperationWithBlock:^{
        NSInteger i = 0;
        while (i < 10) {
            i++;
            NSLog(@"task5:%ld thread:%@",i,NSThread.currentThread);
        }
    }];
    
    [blockOperation4 addDependency:blockOperation5];
    [blockOperation5 addDependency:blockOperation3];
    [operationQueue addOperation:blockOperation4];//设置操作数依赖
    [operationQueue addOperation:blockOperation5];
    [operationQueue addOperation:blockOperation3];

4、自定义NSOperation子类
在官方文档中指出经自定义NSOperation子类有两种形式,并发和非并发,非并发形式只需要继承NSOperation类后实现main方法即可,而并发形式就比较复杂了,接下来会分别介绍两种形式。

非并发的NSOperation自定义子类
@interface TestOperation : NSOperation

@property (nonatomic, copy)id obj;

- (instancetype)initWithObject:(id)obj;

@end

interface TestOperation()
@property (nonatomic, assign, getter=isFinished) BOOL finished;
@property (nonatomic, assign, getter=isExecuting) BOOL executing;
@property (nonatomic, assign, getter=isCancelled) BOOL cancelled;
@end

@implementation TestOperation
@synthesize finished = _finished;
@synthesize executing = _executing;
@synthesize cancelled = _cancelled;

- (instancetype)initWithObject:(id)obj {
    self = [super init];
    if (self) {
        self.obj = obj;
    }
    return self;
}
- (void)start {
    [self main];
}


- (void)main {
    
    self.executing = YES;
    NSInteger i = 0;
    while (i < 10) {
        if (self.isCancelled)
        {
            //任务被取消正确结束前手动设置状态
            self.executing = NO;
            self.finished = YES;
            return;
        }
        i++;
        NSLog(@"TestTask:%ld thread:%@ queue:%@",i,NSThread.currentThread,NSOperationQueue.currentQueue);
    }
    self.executing = NO;
    self.finished = YES;
    
}

- (void)setCancelled:(BOOL)cancelled {
    [self willChangeValueForKey:@"isCancelled"];
    _cancelled = cancelled;
    [self didChangeValueForKey:@"isCancelled"];
}

- (void)setFinished:(BOOL)finished {
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}

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

- (BOOL)isCancelled {
    return _cancelled;
}
- (BOOL)isExecuting {
    return _cancelled;
}


@end

#######试用自定义NSOperation类

    TestOperation *myOperation = [[TestOperation alloc]initWithObject:@"myOperation"];
    myOperation.name = @"private operation";
    myOperation.completionBlock = ^{
        NSLog(@"my operation completed!");
    };
//    [myOperation start];
    [self.operationQueue addOperation:myOperation];

    NSLog(@"isFinished is %d",myOperation.isFinished);
    NSLog(@"isExecuting is %d",myOperation.isExecuting);
    NSLog(@"isCancelled is %d",myOperation.isCancelled);
    NSLog(@"operations is %@",self.operationQueue.operations);
    

认真的: NSLog(@"isFinished is %d",myOperation.isFinished); NSLog(@"operations is %@",self.operationQueue.operations);isFinised一直是0,并且operations里还是包含了myOperation,不知道为什么任务执行完了,isFinished没有变化,同时operations里仍然有自定义的类。但是在主队列里,isFinished会是1。我测试了2个小时,仍然没找到原因,有知道的小伙伴请告知我

祭出demo

更新时间2018-08-24

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

推荐阅读更多精彩内容