NSOperation与网络封装(上)

关于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的实现就留到下一篇文章,敬请期待!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容