關(guān)于NSOperation
- 基于GCD们颜,NSOperation是一個(gè)基于GCD封裝的類偶芍。
- Command纬黎,通過NSOperation可實(shí)現(xiàn)Command這種設(shè)計(jì)模式太援。
- 可創(chuàng)建依賴關(guān)系闽晦。
- 通過NSOperationQueue實(shí)現(xiàn)隊(duì)列任務(wù)并可設(shè)置執(zhí)行優(yōu)先級(jí)。
基于上面這些特點(diǎn)提岔,它很適合用來做網(wǎng)絡(luò)請(qǐng)求封裝仙蛉。尤其是需要將任務(wù)組合起來的,如上傳隊(duì)列和下載隊(duì)列碱蒙。
分析需求荠瘪,定義接口
在開始實(shí)現(xiàn)想法前畫一下圖是一個(gè)很好的習(xí)慣,有助于整理自己的思維并逐步推進(jìn)赛惩。
- Caller:上層調(diào)用者哀墓,該類負(fù)責(zé)構(gòu)造并持有Operation。
- Operation:子類化的NSOperation喷兼。
- Network:網(wǎng)絡(luò)封裝篮绰,本篇不細(xì)說這部分。
實(shí)現(xiàn)一個(gè)大目標(biāo)前季惯,先從小目標(biāo)入手吠各,逐個(gè)完成。先考慮一下我們這個(gè)Operation需要什么功能勉抓。
- 開始任務(wù):繼承自NSOperation后就有一個(gè)start方法贾漏。
- 取消任務(wù):同樣繼承自NSOperation
- 請(qǐng)求參數(shù):通過構(gòu)造方法接收
- 請(qǐng)求結(jié)果:作為一個(gè)Readonly屬性,包括請(qǐng)求成功的結(jié)果藕筋,錯(cuò)誤纵散,請(qǐng)求進(jìn)度。
@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
先實(shí)現(xiàn)最基本的需求隐圾,這里我們需要一個(gè)網(wǎng)絡(luò)封裝類伍掀。Operation只是一個(gè)網(wǎng)絡(luò)請(qǐng)求封裝,從UML圖可以看出來翎承,實(shí)際工作的是另一個(gè)網(wǎng)絡(luò)封裝硕盹。面向?qū)ο蟮脑O(shè)計(jì)原則,保持對(duì)象的功能單一叨咖。
我們這里還卻一個(gè)網(wǎng)絡(luò)封裝類瘩例,但這里只談接口不談實(shí)現(xiàn)啊胶。所謂的面向接口編程,這點(diǎn)很重要垛贤。第一版的需求先將網(wǎng)絡(luò)封裝私有化焰坪。
@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
這不是一個(gè)很嚴(yán)禁的接口,但對(duì)于本文來說足夠了聘惦。實(shí)際項(xiàng)目里需要根據(jù)需求來修改某饰。
Operation的實(shí)現(xiàn)
- executing和finished這兩個(gè)屬性需要重載,因?yàn)槲覀兾磥硇枰獙peration放入NSOperationQueue里進(jìn)行的善绎,所以需要重載這兩個(gè)屬性來控制任務(wù)的生命周期黔漂。
@property (nonatomic, assign, getter=isExecuting) BOOL executing;
@property (nonatomic, assign, getter=isFinished) BOOL finished;
@implementation
// 因?yàn)楦割惖膶傩允荝eadonly的,重載時(shí)如果需要setter的話則需要手動(dòng)合成禀酱。
@synthesize finished = _finished, executing = _executing;
// 這里需要實(shí)現(xiàn)KVO相關(guān)的方法炬守,NSOperationQueue是通過KVO來判斷任務(wù)狀態(tài)的
- (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。這里有一個(gè)非常重要的要點(diǎn)剂跟,在NSOperationQueue里等候中任務(wù)如果設(shè)了isFinished這個(gè)flag减途,那么整個(gè)隊(duì)列都會(huì)被廢掉,余下的任務(wù)將無法執(zhí)行曹洽,還會(huì)偶爾出現(xiàn)崩潰的情況鳍置。
- (void)start {
if (self.isCancelled) {
self.finished = YES;
return;
}
self.executing = YES;
}
- (void)cancel {
[super cancel];
// 如果正在執(zhí)行中則表示已經(jīng)start過,可以將isFinished設(shè)為yes
if (self.isExecuting) {
self.finished = YES;
self.executing = NO;
}
}
- 實(shí)現(xiàn)請(qǐng)求功能
@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];
// 如果正在執(zhí)行中則表示已經(jīng)start過送淆,可以將isFinished設(shè)為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];
}
總結(jié)
一個(gè)簡(jiǎn)單的請(qǐng)求封裝就這樣實(shí)現(xiàn)了税产,但還是缺乏點(diǎn)什么,有經(jīng)驗(yàn)的人會(huì)發(fā)現(xiàn)這里缺少了回調(diào)偷崩,但即使沒有回調(diào)功能砖第,這個(gè)封裝還是完整的,我們可以將回調(diào)作為一個(gè)擴(kuò)展功能來實(shí)現(xiàn)环凿,通過Category來分拆功能模塊》欧裕回調(diào)和NSOperationQueue的實(shí)現(xiàn)就留到下一篇文章智听,敬請(qǐng)期待!