摘抄自: http://www.cocoachina.com/articles/28717
为什么要使用NSOperation?
NSOperation提供任务的封装,NSOperationQueue顾名思义,提供执行队列,可以自动实现多核并行计算,自动管理线程的生命周期,如果是并发的情况,其底层也使用线程池模型来管理,基本上可以说这两个类提供的功能覆盖了GCD,并且提供了更多可定制的开发方式,开发者可以按需选择。
NSOperation把封装好的任务交给不同的NSOperationQueue即可进行串行或并发队列的执行。
通常情况下,任务会交给NSOperation类的一个方法,main或者start方法,所以我们要自定义继承NSOperation类的话,需要重写相关方法。
NSOperation 常用属性和方法
重写的方法
// 对于并发的Operation需要重写改方法
- (void)start;
// 非并发的Operation需要重写该方法
- (void)main;
相关属性
// 任务是否取消(只读) 自定义子类,需重写该属性
@property (readonly, getter=isCancelled) BOOL cancelled;
// 可取消操作,实质是标记 isCancelled 状态,自定义子类,需利用该方法标记取消状态
- (void)cancel;
// 任务是否正在执行(只读),自定义子类,需重写该属性
@property (readonly, getter=isExecuting) BOOL executing;
// 任务是否结束(只读),自定义子类,需重写该属性
// 如果为YES,则队列会将任务移除队列
@property (readonly, getter=isFinished) BOOL finished;
// 判断任务是否为并发(只读),默认返回NO
// 自定义子类,需重写getter方法,并返回YES
@property (readonly, getter=isAsynchronous) BOOL asynchronous;
// 任务是否准备就绪(只读)
// 对于加入队列的任务来说,ready为YES,则表示该任务即将开始执行
// 如果存在依赖关系的任务没有执行完,则ready为NO
@property (readonly, getter=isReady) BOOL ready;
操作同步
// 添加任务依赖
- (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;
// 会在当前操作执行完毕时调用completionBlock
@property (nullable, copy) void (^completionBlock)(void);
// 阻塞当前线程,直到该操作结束,可用于线程执行顺序的同步
- (void)waitUntilFinished;
NSOperationQueue 常用属性和方法
添加任务
// 向队列中添加一个任务
- (void)addOperation:(NSOperation *)op;
// 向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
- (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;
// 队列支持的最大任务并发数
@property NSInteger maxConcurrentOperationCount;
// 队列是否挂起
@property (getter=isSuspended) BOOL suspended;
// 队列名字
@property (nullable, copy) NSString *name
相关方法
// 取消队列中所有的任务
- (void)cancelAllOperations;
// 阻塞当前线程,直到所有任务完成
- (void)waitUntilAllOperationsAreFinished;
// 类属性,获取当前队列
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue;
// 类属性,获取主队列(并发数为1)
@property (class, readonly, strong) NSOperationQueue *mainQueue;
实现自定义的并发的NSOperation子类
实现并发的自定义子类,需要重写下面几个方法或属性:
start:
把需要执行的任务放在start方法里,任务加到队列后,队列会管理任务并在线程被调度后,调用start方法,不需要调用父类的方法
asynchronous:
表示是否并发执行
executing:
表示任务是否正在执行,需要手动调用KVO方法来进行通知,方便其他类监听了任务的该属性
finished:
表示任务是否结束,需要手动调用KVO方法来进行通知,队列也需要监听改属性的值,用于判断任务是否结束
实现实例:
下载一组图片, 图片下载完成时显示在页面上, 全部下载完成后, 提示下载完成
// AsyncOperation.h
#import <UIKit/UIKit.h>
@interface AsyncOperation : NSOperation
@property(nonatomic,strong) NSString *url;
@property(nonatomic,copy) void(^finishBlock)(UIImage *image);
@end
// AsyncOperation.m
#import "AsyncOperation.h"
@interface AsyncOperation()
@property (readwrite, getter=isExecuting) BOOL executing;
@property (readwrite, getter=isFinished) BOOL finished;
@end
@implementation AsyncOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
#pragma mark - 重写start方法开启任务下载
- (void)start{
@autoreleasepool {
self.executing = YES;
if (self.isCancelled) {
[self done];
return;
}
// 任务
[self downloadPicWithUrlStr:self.url completionHandler:^(UIImage *img) {
[self done];
if (self.finishBlock) {
self.finishBlock(img);
}
}];
}
}
#pragma mark - 下载图片
- (void)downloadPicWithUrlStr:(NSString *)urlStr completionHandler:(void(^)(UIImage *img))completionHandler{
NSURL *url = [NSURL URLWithString:urlStr];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (!error && data) {
UIImage *image = [UIImage imageWithData:data];
!completionHandler ? :completionHandler(image);
}else{
!completionHandler ? :completionHandler(nil);
}
}];
[dataTask resume];
}
#pragma mark - 任务完成标记
- (void)done{
self.executing = NO;
self.finished = YES;
}
#pragma mark - 固定要重写的setter/getter
- (void)setExecuting:(BOOL)executing{
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (BOOL)isExecuting{
return _executing;
}
- (void)setFinished:(BOOL)finished{
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (BOOL)isFinished{
return _finished;
}
- (BOOL)isAsynchronous{
return YES;
}
@end
// 调用
#import "AsyncRequestViewController.h"
#import "AsyncOperation.h"
@interface AsyncRequestViewController ()
@property(nonatomic,strong) NSArray *imageList;
@property(nonatomic,strong) UILabel *messageLabel;
@property(nonatomic,strong) UIView *mainView;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// self.imageList = @[...]; 给图片链接数组赋值
// 创建子控件...
}
-(void)downImage_operation{
self.messageLabel.text = @"正在下载...";
for (UIView *view in self.mainView.subviews) {
[view removeFromSuperview];
}
// 下载完成的回调
NSBlockOperation *finishedOp = [NSBlockOperation blockOperationWithBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.messageLabel.text = @"下载完成!";
}];
}];
// 并发队列
NSOperationQueue *queue = [NSOperationQueue new];
// 循环创建下载任务
for (int i=0; i<self.imageList.count; i++) {
NSString *url = self.imageList[i];
AsyncOperation *op = [[AsyncOperation alloc] init];
op.url = url;
[op setFinishBlock:^(UIImage *image) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImageView *imageview = [[UIImageView alloc] initWithFrame:CGRectMake(x, y, w, h)];
[self.mainView addSubview:imageview];
imageview.image = image;
}];
}];
[queue addOperation:op];
// 添加依赖关系
[finishedOp addDependency:op];
}
[queue addOperation:finishedOp];
}
总结
如何开始自定义一个并发的NSOperation?
1.
去NSOperation类文件中将以下两个属性粘贴过来:
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
2.
将readonly改为readwrite
@property (readwrite, getter=isExecuting) BOOL executing;
@property (readwrite, getter=isFinished) BOOL finished;
并通过@synthesize添加别名, 使其可以添加setter方法
@synthesize executing = _executing;
@synthesize finished = _finished;
3.
为executing/finished 两个属性添加 setter/getter方法, setter方法应使用kvo通知(NSOperationQueue会监听这两个属性的kvo通知)
以executing 属性为例
- (void)setExecuting:(BOOL)executing{
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- (BOOL)isExecuting{
return _executing;
}
4.
重写-(BOOL)isAsynchronous;方法,返回YES
- (BOOL)isAsynchronous{
return YES;
}
5.
重写-(void)start;方法,
开始任务时:self.executing = YES;
结束任务时:self.executing = NO; self.finished = YES;
- (void)start{
@autoreleasepool {
// 开始任务
self.executing = YES;
if (self.isCancelled) {
// 结束任务
self.executing = NO;
self.finished = YES;
return;
}
// 任务
[self downloadPicWithUrlStr:self.url completionHandler:^(UIImage *img) {
// 耗时操作完成 ...
// 结束任务
self.executing = NO;
self.finished = YES;
}];
}
}
@synthesize 的作用: 给实例变量起一个别名,或者说为同一个变量添加两个名字
使用场景1: 为一个属性同时添加getter和setter方法
使用场景2: 为一个只读属性添加setter方法
网友评论