iOS与多线程(二) —— NSOperation实现多并发之创建任务

版本记录

版本号 时间
V1.0 2017.08.15

前言

信号量机制是多线程通信中的比较重要的一部分,对于NSOperation可以设置并发数,但是对于GCD就不能设置并发数了,那么就只能靠信号量机制了。接下来这几篇就会详细的说一下并发机制。感兴趣的可以看这几篇文章。
1. iOS与多线程(一) —— GCD中的信号量及几个重要函数

先认识一下NSOperation

NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更简单易用、代码可读性也更高。

NSOperation并没有开辟线程的能力,在多线程并发中需要NSOperationQueue的配合,才能实现异步并发操作。


NSOperation相关API

下面我们就看一下NSOperation的相关API。

@interface NSOperation : NSObject {
@private
    id _private;
    int32_t _private1;
#if __LP64__
    int32_t _private1b;
#endif
}

- (void)start;
- (void)main;

@property (readonly, getter=isCancelled) BOOL cancelled;
- (void)cancel;

@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@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);

- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);

@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



NS_CLASS_AVAILABLE(10_6, 4_0)
@interface NSBlockOperation : NSOperation {
@private
    id _private2;
    void *_reserved2;
}

+ (instancetype)blockOperationWithBlock:(void (^)(void))block;

- (void)addExecutionBlock:(void (^)(void))block;
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;

@end


NS_CLASS_AVAILABLE(10_5, 2_0)
NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available")
@interface NSInvocationOperation : NSOperation {
@private
    id _inv;
    id _exception;
    void *_reserved2;
}

- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
- (instancetype)initWithInvocation:(NSInvocation *)inv NS_DESIGNATED_INITIALIZER;

@property (readonly, retain) NSInvocation *invocation;

@property (nullable, readonly, retain) id result;

@end

FOUNDATION_EXPORT NSExceptionName const NSInvocationOperationVoidResultException NS_AVAILABLE(10_5, 2_0);
FOUNDATION_EXPORT NSExceptionName const NSInvocationOperationCancelledException NS_AVAILABLE(10_5, 2_0);

static const NSInteger NSOperationQueueDefaultMaxConcurrentOperationCount = -1;

NS_CLASS_AVAILABLE(10_5, 2_0)
@interface NSOperationQueue : NSObject {
@private
    id _private;
    void *_reserved;
}

- (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);

@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);

- (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

NS_ASSUME_NONNULL_END

大家可以看到,上面的API中包括NSOperation,也包括NSBlockOperationNSInvocationOperationNSOperationQueue三个相关的类和API接口。


NSOperation多线程实现步骤

NSOperation相当于GCD中的任务,NSOperationQueue相当于GCD中的队列,其实现多线程并发主要有以下几个步骤:

  • 创建任务:注意就是将任务封装在NSOperation子类的对象中。
  • 创建队列:创建NSOperationQueue对象。
  • 加入任务到队列:将任务NSOperation对象,添加到NSOperationQueue对象中。

下面我们就详细的说一下这几个步骤。

1. 创建任务

这里需要说明一下,NSOperation只是一个抽象类,不能用它直接创建任务,我们一般都是用其子类创建任务,它的子类分为三种:

  • NSInvocationOperation
  • NSBlockOperation
  • 自定义的NSOperation子类

下面我们就看一下具体创建任务的情况。

使用子类NSInvocationOperation创建任务

先看代码。

#import "JJInvocationOperationVC.h"

@interface JJInvocationOperationVC ()

@end

@implementation JJInvocationOperationVC

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationDidInstance) object:nil];
    
    //调用start方法开始
    [operation start];
}

#pragma mark - Action && Notification

- (void)invocationOperationDidInstance
{
    NSLog(@"NSInvocationOperation");
    NSLog(@"thread = %@",[NSThread currentThread]);
}

@end

下面看输出结果

2017-08-16 16:30:30.062453+0800 JJOC[10120:4497369] NSInvocationOperation
2017-08-16 16:30:30.062568+0800 JJOC[10120:4497369] thread = <NSThread: 0x170072e00>{number = 1, name = main}

从上面可以看出来,只是创建了一个任务,还是在主线程执行的,并没有开启新的线程。

使用子类NSBlockOperation创建任务

还是直接看代码。

#import "JJBlockOperationVC.h"

@interface JJBlockOperationVC ()

@end

@implementation JJBlockOperationVC

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation");
        NSLog(@"thread = %@",[NSThread currentThread]);
    }];
    
    //调用start方法开始
    [operation start];
}

@end

下面看输出结果

2017-08-16 16:35:06.855774+0800 JJOC[10123:4499602] NSBlockOperation
2017-08-16 16:35:06.855873+0800 JJOC[10123:4499602] thread = <NSThread: 0x170071000>{number = 1, name = main}

从上面可以看出来,只是创建了一个任务,还是在主线程执行的,并没有开启新的线程。

注意:NSBlockOperation 还有一个方法addExecutionBlock,这个方法可以在主线程或者其他线程执行,也可以这么理解这个方法可能开启线程也可能不开启新的线程只在主线程执行,下面我们看一下代码。

#import "JJBlockOperationVC.h"

@interface JJBlockOperationVC ()

@end

@implementation JJBlockOperationVC

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation");
        NSLog(@"thread = %@",[NSThread currentThread]);

    }];
    
    [operation addExecutionBlock:^{
        NSLog(@"thread_1 = %@",[NSThread currentThread]);
    }];
    
    [operation addExecutionBlock:^{
        NSLog(@"thread_2 = %@",[NSThread currentThread]);
    }];
    
    [operation addExecutionBlock:^{
        NSLog(@"thread_3 = %@",[NSThread currentThread]);
    }];
    
    //调用start方法开始
    [operation start];
}

@end

下面看输出结果

2017-08-16 16:38:20.070254+0800 JJOC[10126:4500144] NSBlockOperation
2017-08-16 16:38:20.070436+0800 JJOC[10126:4500144] thread = <NSThread: 0x17007bf00>{number = 1, name = main}
2017-08-16 16:38:20.070446+0800 JJOC[10126:4500181] thread_1 = <NSThread: 0x174264f40>{number = 3, name = (null)}
2017-08-16 16:38:20.070501+0800 JJOC[10126:4500144] thread_2 = <NSThread: 0x17007bf00>{number = 1, name = main}
2017-08-16 16:38:20.070509+0800 JJOC[10126:4500181] thread_3 = <NSThread: 0x174264f40>{number = 3, name = (null)}

大家可以发现,开启了一个number = 3的线程,thread_2并没有开启新线程,仍在主线程执行。

使用自定义的NSOperation子类创建任务

下面还是直接看代码。

1. JJCustomOperationVC.h
#import <UIKit/UIKit.h>

@interface JJCustomOperationVC : UIViewController

@end
2. JJCustomOperationVC.m
#import "JJCustomOperationVC.h"
#import "JJOperation.h"

@interface JJCustomOperationVC ()

@end

@implementation JJCustomOperationVC

#pragma mark - Override Base Function

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    JJOperation *operation = [[JJOperation alloc] init];
    
    [operation start];
}

@end
3. JJOperation.h
#import <Foundation/Foundation.h>

@interface JJOperation : NSOperation

@end
4. JJOperation.m
#import "JJOperation.h"

@implementation JJOperation

#pragma mark - Override Base Function

//重写main方法,里面存放需要执行的任务

- (void)main
{
    NSLog(@"自定义的Operation");
    NSLog(@"thread = %@",[NSThread currentThread]);
}

@end

下面我们看输出结果

2017-08-16 16:51:08.461304+0800 JJOC[10130:4502372] 自定义的Operation
2017-08-16 16:51:08.461408+0800 JJOC[10130:4502372] thread = <NSThread: 0x17406c0c0>{number = 1, name = main}

从输出我们可以看到,还是没有开启新的线程。

参考文章

1. iOS多线程--彻底学会多线程之『NSOperation』
2. iOS多线程--彻底学会多线程之『pthread、NSThread』
3. iOS多线程--彻底学会多线程之『GCD』
4. iOS多线程--彻底学会多线程之『RunLoop』

后记

未完,待续~~~

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

推荐阅读更多精彩内容