技 术 文 章 / 超 人
NSOperation介绍
NSOperation是一个抽象类,因此不能直接使用NSOperation,需要调用NSOperation的子类来进行操作。虽然NSOperation是抽象的,但是内部的基本实现包含了重要的逻辑来协调任务安全执行。
NSOperation一般与NSOperationQueue配合使用,来实现多线程操作。
如果单独使用NSOperation来执行操作,不会开启新线程,而是在主线程中完成任务。
NSOperationQueue介绍 官方
NSOperationQueue是调节一组操作的执行队列,将添加到队列中到NSOperation实例保存在队列中,直到被明确取消或者完成操作任务。队列中到操作本身按照优先级或者操作对象之间到依赖关系进行排列执行。
NSOperationQueue默认是并发队列,默认时maxConcurrentOperationCount为 -1 ,即无穷大。,当maxConcurrentOperationCount值设置为2或者为1时,是串行队列。
NSOperation子类
NSOperation的使用方式有三种
- 自定义类继承NSOperation ,在main方法中实现内部操作的封装,该方法常用实用。
- NSInvocationOperation 子类
- NSBlockOperation 子类
NSInvocationOperation
继承自NSOperation,由系统封装的类。系统给予的方法有两种
-
-(nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg
该方法用于绑定执行操作的方法,参数说明如下:
target:绑定方法所在的对象
sel:target对象中方法名称
arg:方法中所带的参数,如果没有参数可以为nil -
-(instancetype)initWithInvocation:(NSInvocation *)inv
- 通过传入一个NSInvocation对象初始化了一个NSInvocationOperation对象。NSInvocation对象通过传入一个方法签名进行初始化,并且给NSInvocation对象设置了target和selector。
-
使用
方法一
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(methodName) object:nil];
[op1 start];
方法二
NSMethodSignature *sign = [[self class] instanceMethodSignatureForSelector:@selector(demo)];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sign];
inv.target = self;
inv.selector = @selector(demo);
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithInvocation:inv];
[op2 start];
NSBlockOperation
继承自NSOperation,由系统封装的类。系统给予的方法有两种
+ (instancetype)blockOperationWithBlock:(void (^)(void))block
类方法(静态方法),可以通过类方法直接初始化一个blockOperation对象。直接在block中写入执行的操作
- (void)addExecutionBlock:(void (^)(void))block
对象方法(动态方法),可以在原本的blockOperationWithBlock之后在添加其他操作,但并不是blockOperationWithBlock中执行完才执行addExecutionBlock中的操作,addExecutionBlock与blockOperationWithBlock是并发执行的。
- 使用
方法一
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
[op1 start];
// 单独使用NSBlockOperation输出结果:
NSOperation[1839:133702] <NSThread: 0x608000076b00>{number = 1, name = main}
//在主线程中完成,而不是子线程
方法二
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务1- %@", [NSThread currentThread]);
}];
// 添加额外的任务
[op1 addExecutionBlock:^{
NSLog(@"任务2- %@", [NSThread currentThread]);
}];
[op1 addExecutionBlock:^{
NSLog(@"任务3- %@", [NSThread currentThread]);
}];
[op1 start];
// 输出结果:
2017-02-08 22:41:54.871 NSOperation[1884:142063] 任务1- <NSThread: 0x60800007cec0>{number = 1, name = main}
2017-02-08 22:41:54.871 NSOperation[1884:142100] 任务3- <NSThread: 0x6080002699c0>{number = 4, name = (null)}
2017-02-08 22:41:54.871 NSOperation[1884:142101] 任务2- <NSThread: 0x608000269800>{number = 3, name = (null)}
//添加额外任务后,系统自动开启了子线程去执行任务,执行是并发执行,
NSOperation的自定义
要自定义NSOperation,需要先了解NSOperation中的方法属性
NS_CLASS_AVAILABLE(10_5, 2_0)
@interface NSOperation : NSObject {
@private
id _private;
int32_t _private1;
#if __LP64__
int32_t _private1b;
#endif
}
/* 启动方法,当添加或者绑定了操作后,调用该方法会启动执行任务 */
- (void)start;
/* 主方法,简单的自定义NSOperation,可以重写main方法的实现,在main中写入具体执行的操作 */
- (void)main;
/* 是否取消操作的判断属性,当调用cancel后,改属性为YES,表示操作已被取消 */
@property (readonly, getter=isCancelled) BOOL cancelled;
/* 当需要取消操作时调用该方法,内部改变了cancelled值 */
- (void)cancel;
/* */
@property (readonly, getter=isExecuting) BOOL executing;
/* 是否完成操作的判断属性,当操作完成后该值为YES */
@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 API_AVAILABLE(macos(10.8), ios(7.0), watchos(2.0), tvos(9.0));
/* */
@property (readonly, getter=isReady) BOOL ready;
/* 在当前操作对象中添加依赖操作 */
/* [op1 addDependency:op2] 表示当op2完成操作后,op1才执行 */
- (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;
/* 回调属性,当前NSOperation对象执行完操作后,可以在改属性回调中收到消息,可以执行操作。但该回调属性中的操作也是并发执行的 */
@property (nullable, copy) void (^completionBlock)(void) API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
- (void)waitUntilFinished API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
@property double threadPriority API_DEPRECATED("Not supported", macos(10.6,10.10), ios(4.0,8.0), watchos(2.0,2.0), tvos(9.0,9.0));
@property NSQualityOfService qualityOfService API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
@end
自定义NSOperation案例说明
当下的直播软件越来越多,每个直播都有刷礼物功能。本次的案例就是显示用户刷的礼物
- 首先
创建一个继承自NSOperation的类
SMGiftOperation.h
#import <UIKit/UIKit.h>
#import "SMGiftNewAlertView.h"
@class SMGiftNewModel;
@interface SMGiftOperation : NSOperation
/* 礼物对象 用来显示礼物的view */
@property (nonatomic,strong) SMGiftNewAlertView *giftView;
/* 礼物对象显示到的父界面 */
@property (nonatomic,strong) UIView *giftPresentView;
/* 礼物对象模型 */
@property (nonatomic,strong) SMGiftNewModel *model;
/* 礼物弹窗显示的位置 */
@property (nonatomic,assign) NSInteger index;
/* 用户唯一标示,记录礼物信息 */
@property (nonatomic,copy) NSString *userID;
// 回调参数增加了结束时礼物累计数
/**
viewY:礼物所显示在父界面的Y轴位置
finishedBlock:当操作完成后收到当回调
*/
+ (instancetype)SMGiftOperationWithUserID:(NSString *)userID model:(SMGiftNewModel *)model presentView:(UIView *)view index:(NSInteger)index forViewY:(CGFloat)viewY finishedBlock:(void(^)(BOOL result,NSInteger finishCount))finishedBlock;
@end
SMGiftOperation.m
#import "SMGiftOperation.h"
#import "SMGiftNewModel.h"
@interface SMGiftOperation()
{}
/* 执行完成的回调 finishCount表示礼物刷的数量 */
@property (nonatomic,copy) void(^finishedBlock)(BOOL result,NSInteger finishCount);
@end
@implementation SMGiftOperation
+ (instancetype)SMGiftOperationWithUserID:(NSString *)userID model:(SMGiftNewModel *)model presentView:(UIView *)view index:(NSInteger)index forViewY:(CGFloat)viewY finishedBlock:(void(^)(BOOL result,NSInteger finishCount))finishedBlock {
/* 通过对象方法内部创建一个自定义的NSPOperation对象 */
SMGiftOperation *op = [[SMGiftOperation alloc] init];
/* 配置操作对象所需要执行的model数据 */
op.giftView = [[LYKeenkeepLiveVoiceGiftNewAlertView alloc] initWithModel:model];
/* 把礼物界面添加到需要显示到父界面 */
[view addSubview:op.giftView];
/* 根据index判断礼物需要显示到Y位置 */
CGFloat y = index == 0 ? (viewY+10*kScale) : (viewY+60*kScale);
/* 设置约束 */
op.giftView.sd_layout
.heightIs(40*kScale)
.yIs(y)
.xIs(-view.size.width);
[op.giftView setupAutoWidthWithRightView:op.giftView.loveNumberLabel rightMargin:23*kScale];
[op.giftView updateLayout];
/* 配置对象的基本属性 */
op.index = index;
op.giftPresentView = view;
op.model = model;
op.finishedBlock = finishedBlock;
return op;
}
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
/* 重写start方法 当把自定义当NSOperation对象加入到NSOperationQueue对象中时,系统会自动调用start方法,当然也可以重写main方法 */
- (void)start {
/* 判断是否取消,如果取消了就不执行操作 */
if ([self isCancelled]) {
return;
}
/* 礼物是从0 到 N 叠加显示的,所以第一次是0先回调0,外部收到的回调数量和model的数量相同时,表示礼物数量动画显示完毕,等待移除该礼物的显示*/
self.finishedBlock(NO,0);
/* 在主线程中刷新礼物UI的显示 */
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
/* 配置礼物的model数据,这个数据可以根据自己的显示需求自行定义,所以就不把model代码贴出来了 */
_giftView.model = _model;
/* 把礼物界面添加到父界面 */
[self.giftPresentView addSubview:_giftView];
/* 执行礼物界面的动画显示,首先出现在界面,然后开始叠加礼物数量,当礼物叠加数量和礼物总数相同时,移除礼物界面 */
[self.giftView animateWithCompleteBlock:^(BOOL finished,NSInteger finishCount) {
self.finishedBlock(finished,finishCount);
if (finished) {
/* 主线程中修改礼物约束来移动礼物 */
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIView animateWithDuration:0.3 animations:^{
self.giftView.sd_layout
.xIs([UIScreen mainScreen].bounds.size.width);
[self.giftView updateLayout];
} completion:^(BOOL finished) {
}];
});
}
}];
}];
}
@end
由于代码比较简单,所以finished,cancelled等属性没有去改变。
网友评论