关于NSOperation
- 基于GCD,NSOperation是一个基于GCD封装的类。
- Command,通过NSOperation可实现Command这种设计模式。
- 可创建依赖关系。
- 通过NSOperationQueue实现队列任务并可设置执行优先级。
基于上面这些特点,它很适合用来做网络请求封装。尤其是需要将任务组合起来的,如上传队列和下载队列。
分析需求,定义接口
在开始实现想法前画一下图是一个很好的习惯,有助于整理自己的思维并逐步推进。
- Caller:上层调用者,该类负责构造并持有Operation。
- Operation:子类化的NSOperation。
- Network:网络封装,本篇不细说这部分。
实现一个大目标前,先从小目标入手,逐个完成。先考虑一下我们这个Operation需要什么功能。
- 开始任务:继承自NSOperation后就有一个start方法。
- 取消任务:同样继承自NSOperation
- 请求参数:通过构造方法接收
- 请求结果:作为一个Readonly属性,包括请求成功的结果,错误,请求进度。
@interface CustomOperation : NSOperation
@property (nonatomic, readonly) id result;
@property (nonatomic, readonly) NSError *error;
@property (nonatomic, readonly) double progress;
+ (instancetype)getWithUrlString:(NSString *)urlString
parameters:(NSDictionary<NSString *, NSString *> *)parameters;
+ (instancetype)postWithUrlString:(NSString *)urlString
parameters:(NSDictionary<NSString *, NSString *> *)parameters;
+ (instancetype)downloadWithUrlString:(NSString *)urlString;
+ (instancetype)uploadWithUrlString:(NSString *)urlString
parameters:(NSDictionary<NSString *, NSString *> *)parameters
data:(NSData *)data;
@end
先实现最基本的需求,这里我们需要一个网络封装类。Operation只是一个网络请求封装,从UML图可以看出来,实际工作的是另一个网络封装。面向对象的设计原则,保持对象的功能单一。
我们这里还却一个网络封装类,但这里只谈接口不谈实现。所谓的面向接口编程,这点很重要。第一版的需求先将网络封装私有化。
@interface Network : NSObject
+ (instancetype)share;
- (NSURLSessionDataTask *)dataTaskWithUrlString:(NSString *)urlString
method:(NSString *)method
parameters:(NSDictionary<NSString *, NSString *> *)parameters
callBack:(void(^)(NSError *,id result))callBack;
- (NSURLSessionDownloadTask *)downloadTaskWithUrlString:(NSString *)urlString
progress:(void(^)(double))progress
callBack:(void(^)(NSError *,id result))callBack;
- (NSURLSessionDataTask *)uploadTaskWithUrlString:(NSString *)urlString
parameters:(NSDictionary<NSString *, NSString *> *)parameters
uploadData:(NSData *)data
progress:(void(^)(double))progress
callBack:(void(^)(NSError *, id result))callBack;
@end
这不是一个很严禁的接口,但对于本文来说足够了。实际项目里需要根据需求来修改。
Operation的实现
- executing和finished这两个属性需要重载,因为我们未来需要将Operation放入NSOperationQueue里进行的,所以需要重载这两个属性来控制任务的生命周期。
@property (nonatomic, assign, getter=isExecuting) BOOL executing;
@property (nonatomic, assign, getter=isFinished) BOOL finished;
@implementation
// 因为父类的属性是Readonly的,重载时如果需要setter的话则需要手动合成。
@synthesize finished = _finished, executing = _executing;
// 这里需要实现KVO相关的方法,NSOperationQueue是通过KVO来判断任务状态的
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
- 重载start和cancel。这里有一个非常重要的要点,在NSOperationQueue里等候中任务如果设了isFinished这个flag,那么整个队列都会被废掉,余下的任务将无法执行,还会偶尔出现崩溃的情况。
- (void)start {
if (self.isCancelled) {
self.finished = YES;
return;
}
self.executing = YES;
}
- (void)cancel {
[super cancel];
// 如果正在执行中则表示已经start过,可以将isFinished设为yes
if (self.isExecuting) {
self.finished = YES;
self.executing = NO;
}
}
- 实现请求功能
@property (nonatomic, readwrite) id result;
@property (nonatomic, readwrite) NSError *error;
@property (nonatomic, readwrite) double progress;
@property (nonatomic, copy) NSString *urlString;
@property (nonatomic, copy) NSDictionary<NSString *, NSString *> *parameters;
@property (nonatomic, copy) NSData *uploadData;
@property (nonatomic, assign) OperationType type;
@property (nonatomic, strong) NSURLSessionTask *task;
+ (instancetype)getWithUrlString:(NSString *)urlString
parameters:(NSDictionary<NSString *, NSString *> *)parameters {
CustomOperation *op = [CustomOperation new];
op.type = OperationTypeGet;
op.urlString = urlString;
op.parameters = parameters;
return op;
}
+ (instancetype)postWithUrlString:(NSString *)urlString
parameters:(NSDictionary<NSString *, NSString *> *)parameters {
CustomOperation *op = [CustomOperation new];
op.type = OperationTypePost;
op.urlString = urlString;
op.parameters = parameters;
return op;
}
+ (instancetype)downloadWithUrlString:(NSString *)urlString {
CustomOperation *op = [CustomOperation new];
op.type = OperationTypeDownload;
op.urlString = urlString;
return op;
}
+ (instancetype)uploadWithUrlString:(NSString *)urlString
parameters:(NSDictionary<NSString *, NSString *> *)parameters
data:(NSData *)data {
CustomOperation *op = [CustomOperation new];
op.type = OperationTypeUpload;
op.urlString = urlString;
op.parameters = parameters;
op.uploadData = data;
return op;
}
- (void)start {
if (self.isCancelled) {
self.finished = YES;
return;
}
[self handleNetwork];
self.executing = YES;
}
- (void)cancel {
[super cancel];
// 如果正在执行中则表示已经start过,可以将isFinished设为yes
if (self.isExecuting) {
self.finished = YES;
self.executing = NO;
}
[self.task cancel];
self.task = nil;
}
- (void)handleNetwork {
Network *network = [Network share];
if (self.type == OperationTypeGet) {
self.task = [network dataTaskWithUrlString:self.urlString
method:@"GET"
parameters:self.parameters callBack:^(NSError *error, id result) {
self.error = error;
self.result = result;
self.finished = YES;
self.executing = NO;
}];
}
if (self.type == OperationTypePost) {
self.task = [network dataTaskWithUrlString:self.urlString
method:@"POST"
parameters:self.parameters callBack:^(NSError *error, id result) {
self.error = error;
self.result = result;
self.finished = YES;
self.executing = NO;
}];
}
if (self.type == OperationTypeDownload) {
self.task = [network downloadTaskWithUrlString:self.urlString
progress:^(double progress) {
self.progress = progress;
} callBack:^(NSError *error, id result) {
self.error = error;
self.result = result;
self.finished = YES;
self.executing = NO;
}];
}
if (self.type == OperationTypeUpload) {
self.task = [network uploadTaskWithUrlString:self.urlString
parameters:self.parameters
uploadData:self.uploadData
progress:^(double progress) {
self.progress = progress;
} callBack:^(NSError *error, id result) {
self.error = error;
self.result = result;
self.finished = YES;
self.executing = NO;
}];
}
[self.task resume];
}
总结
一个简单的请求封装就这样实现了,但还是缺乏点什么,有经验的人会发现这里缺少了回调,但即使没有回调功能,这个封装还是完整的,我们可以将回调作为一个扩展功能来实现,通过Category来分拆功能模块。回调和NSOperationQueue的实现就留到下一篇文章,敬请期待!