關(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)先級。
基于上面這些特點(diǎn),它很適合用來做網(wǎng)絡(luò)請求封裝。尤其是需要將任務(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
- 請求參數(shù):通過構(gòu)造方法接收
- 請求結(jié)果:作為一個(gè)Readonly屬性,包括請求成功的結(jié)果,錯(cuò)誤,請求進(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ò)請求封裝,從UML圖可以看出來,實(shí)際工作的是另一個(gè)網(wǎng)絡(luò)封裝。面向?qū)ο蟮脑O(shè)計(jì)原則,保持對象的功能單一。
我們這里還卻一個(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)禁的接口,但對于本文來說足夠了。實(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的話則需要手動合成。
@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ì)列都會被廢掉,余下的任務(wù)將無法執(zhí)行,還會偶爾出現(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)請求功能
@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è)簡單的請求封裝就這樣實(shí)現(xiàn)了,但還是缺乏點(diǎn)什么,有經(jīng)驗(yàn)的人會發(fā)現(xiàn)這里缺少了回調(diào),但即使沒有回調(diào)功能,這個(gè)封裝還是完整的,我們可以將回調(diào)作為一個(gè)擴(kuò)展功能來實(shí)現(xiàn),通過Category來分拆功能模塊。回調(diào)和NSOperationQueue的實(shí)現(xiàn)就留到下一篇文章,敬請期待!