美文网首页
自定义NSOperation

自定义NSOperation

作者: ImmortalSummer | 来源:发表于2021-03-16 17:10 被阅读0次

摘抄自: 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方法

相关文章

网友评论

      本文标题:自定义NSOperation

      本文链接:https://www.haomeiwen.com/subject/leudcltx.html