NSOperation與網(wǎng)絡(luò)封裝(上)

關(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ì)列。

分析需求,定義接口

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

在開始實(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需要什么功能。

  1. 開始任務(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)就留到下一篇文章,敬請期待!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容