NSOperation与网络封装(上)

作者: Noah1985 | 来源:发表于2016-11-22 12:50 被阅读1029次

关于NSOperation

  • 基于GCD,NSOperation是一个基于GCD封装的类。
  • Command,通过NSOperation可实现Command这种设计模式。
  • 可创建依赖关系。
  • 通过NSOperationQueue实现队列任务并可设置执行优先级。

基于上面这些特点,它很适合用来做网络请求封装。尤其是需要将任务组合起来的,如上传队列和下载队列。

分析需求,定义接口

Screen Shot 2016-11-22 at 10.42.39 AM.png

在开始实现想法前画一下图是一个很好的习惯,有助于整理自己的思维并逐步推进。

  • Caller:上层调用者,该类负责构造并持有Operation。
  • Operation:子类化的NSOperation。
  • Network:网络封装,本篇不细说这部分。

实现一个大目标前,先从小目标入手,逐个完成。先考虑一下我们这个Operation需要什么功能。

  1. 开始任务:继承自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的实现就留到下一篇文章,敬请期待!

相关文章

网友评论

  • coding_Zhang:你好,有个疑问,实现了 isFinished 的 set方法,系统不是会自动调用willChangeValueForKey 和didChangeValueForKey ,为什么需要手动再写一遍呢
  • FDZero:赞一个

本文标题:NSOperation与网络封装(上)

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