iOS網(wǎng)絡(luò)編程(YTKNetwork)

作者:J_Knight_
鏈接:http://www.itdecent.cn/p/89dd444399ce
來源:簡書

一.概述

基于 AFNetworking 進(jìn)行再一次封裝,以及緩存處理。采用了離散式替代傳統(tǒng) AFNetworking 的集約式編程。通過給每一個請求創(chuàng)建一個對象的高度定制性,使 url 以及響應(yīng)方式等內(nèi)容不暴露出來,以增加了一些代碼工作量為代價,更好的做到業(yè)務(wù)層與網(wǎng)絡(luò)層的分離。

二.架構(gòu)

架構(gòu)圖.png

說明:

  1. YTKNetwork 框架將每一個請求實例化,YTKBaseRequest 是所有請求類的基類,YTKRequest 是它的子類。所以如果我們想要發(fā)送一個請求,則需要創(chuàng)建并實例化一個繼承于YTKRequest的自定義的請求類(CustomRequest)并發(fā)送請求。
  2. YTKNetworkAgent 是一個單例,負(fù)責(zé)管理所有的請求類(例如 CustomRequest )。當(dāng) CustomRequest 發(fā)送請求以后,會把自己放在 YTKNetworkAgent 持有的一個字典里,讓其管理自己。
  3. 我們說 YTKNetwork 封裝了 AFNetworking ,實際上是 YTKNetworkAgent 封裝了 AFNetworking ,由它負(fù)責(zé) AFNetworking 請求的發(fā)送和 AFNetworking 的回調(diào)處理。所以如果我們想更換一個第三方網(wǎng)絡(luò)請求庫,就可以在這里更換一下。而 YTKRequest 更多的是只是負(fù)責(zé)緩存的處理。

三.設(shè)計模式

采用命令模式(Command Pattern)設(shè)計模式。
命令模式的簡單介紹:

  • Client:創(chuàng)建具體命令
  • Invoker:命令調(diào)用者
  • Receiver:命令接受者
  • ConcreteCommand:具體命令
  • Command:抽象命令

在 YTKNetwork 中的對應(yīng)應(yīng)用如下表:

對應(yīng)表.png

命令模式的本質(zhì)是對命令的封裝,將發(fā)出命令的責(zé)任和執(zhí)行命令的責(zé)任分割開。
命令模式允許請求的一方和接收的一方獨立開來,使得請求的一方不必知道接收請求的一方的接口,更不必知道請求是怎么被接收,以及操作是否被執(zhí)行、何時被執(zhí)行,以及是怎么被執(zhí)行的。

四.YTKNetwork 各類介紹

類名 職責(zé)
YTKBaseRequest 所有請求類的基類。持有 NSURLSessionTask 實例,responseData,responseObject,error 等重要數(shù)據(jù),提供一些需要子類實現(xiàn)的與網(wǎng)絡(luò)請求相關(guān)的方法,處理回調(diào)的代理和 block,命令 YTKNetworkAgent 發(fā)起網(wǎng)絡(luò)請求。
YTKRequest YTKBaseRequest 的子類。負(fù)責(zé)緩存的處理:請求前查詢緩存;請求后寫入緩存。
YTKNetworkConfig 被 YTKRequest 和 YTKNetworkAgent 訪問。負(fù)責(zé)所有請求的全局配置,例如 baseUrl 和 CDNUrl 等等。
YTKNetworkPrivate 提供 JSON 驗證,appVersion 等輔助性的方法;給 YTKBaseRequest 增加一些分類。
YTKNetworkAgent 真正發(fā)起請求的類。負(fù)責(zé)發(fā)起請求,結(jié)束請求,并持有一個字典來存儲正在執(zhí)行的請求。
YTKBatchRequest 負(fù)責(zé)管理多個 YTKBatchRequest 實例,持有一個數(shù)組來保存 YTKBatchRequest 。支持添加和刪除 YTKBatchRequest 實例。
YTKChainRequest 可以發(fā)起鏈?zhǔn)秸埱?,持有一個數(shù)組來保存所有的請求類。當(dāng)某個請求結(jié)束后才能發(fā)起下一個請求,如果其中有一個請求返回失敗,則認(rèn)定本請求鏈?zhǔn) ?/td>
YTKChainRequestAgent 負(fù)責(zé)管理多個 YTKChainRequestAgent 實例,持有一個數(shù)組來保存 YTKChainRequest。支持添加和刪除 YTKChainRequest 實例。

五.基本使用

單個請求配置

官方的教程建議我們將請求的全局配置是在 AppDelegate.m 文件里,設(shè)定 baseUrl 以及 cdnUrl 等參數(shù)。

- (BOOL)application:(UIApplication *)application 
   didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
   YTKNetworkConfig *config = [YTKNetworkConfig sharedConfig];
   config.baseUrl = @"http://yuantiku.com";
   config.cdnUrl = @"http://fen.bi";
}

如果我們需要新建一個注冊的請求,則需要創(chuàng)建一個繼承于YTKRequest的注冊接口的類RegisterApi,并將針對該請求參數(shù)配置好:

// RegisterApi.h
#import "YTKRequest.h"

@interface RegisterApi : YTKRequest

- (id)initWithUsername:(NSString *)username password:(NSString *)password;

@end


// RegisterApi.m
#import "RegisterApi.h"

@implementation RegisterApi {
    NSString *_username;
    NSString *_password;
}

//初始化的時候?qū)蓚€參數(shù)值傳入
- (id)initWithUsername:(NSString *)username password:(NSString *)password {
    self = [super init];
    if (self) {
        _username = username;
        _password = password;
    }
    return self;
}

//需要和baseUrl拼接的地址
- (NSString *)requestUrl {
    // “ http://www.yuantiku.com ” 在 YTKNetworkConfig 中設(shè)置,這里只填除去域名剩余的網(wǎng)址信息
    return @"/iphone/register";
}

//請求方法,某人是GET
- (YTKRequestMethod)requestMethod {
    return YTKRequestMethodPOST;
}

//請求體
- (id)requestArgument {
    return @{
        @"username": _username,
        @"password": _password
    };
}

@end
單個請求的發(fā)起

還是剛才的注冊 API,在實例化以后,直接調(diào)用startWithCompletionBlockWithSuccess:failure 方法(或start方法)就可以發(fā)起它:

RegisterApi *api = [[RegisterApi alloc] initWithUsername:username password:password];
[api startWithCompletionBlockWithSuccess:^(YTKBaseRequest *request) {
    // 你可以直接在這里使用 self
    NSLog(@"succeed");
} failure:^(YTKBaseRequest *request) {
    // 你可以直接在這里使用 self
    NSLog(@"failed");
}];

六.源碼解析

一.封裝請求,調(diào)用 start 方法

startWithCompletionBlockWithSuccess:failure

來看一下YTKNetwork做了什么:

//YTKBaseRequest.m
//傳入成功和失敗的block,并保存起來
- (void)startWithCompletionBlockWithSuccess:(YTKRequestCompletionBlock)success
                                    failure:(YTKRequestCompletionBlock)failure {
    //保存成功和失敗的回調(diào)block,便于將來調(diào)用
    [self setCompletionBlockWithSuccess:success failure:failure];
    //發(fā)起請求
    [self start];
}

//保存成功和失敗的block
- (void)setCompletionBlockWithSuccess:(YTKRequestCompletionBlock)success
                              failure:(YTKRequestCompletionBlock)failure {
    self.successCompletionBlock = success;
    self.failureCompletionBlock = failure;
}

當(dāng)保存完成功和失敗的 bloc k以后,調(diào)用 start 方法,于是來到了 YTKRequest 類(注意,雖然 YTKBaseRequest 也實現(xiàn)了 start 方法,但是由于 YTKRequest 類是它的子類并也實現(xiàn)了 start 方法,所以這里最先走的是 YTKRequest 類的 start 方法):

- (void)start {
    //如果忽略緩存
    //ignoreCache屬性是用戶手動設(shè)置的,如果用戶強(qiáng)制忽略緩存,則無論是否緩存是否存在,直接發(fā)送請求。
    if (self.ignoreCache) {
        //調(diào)用無緩存請求方法
        [self startWithoutCache];
        return;
    }

    //如果存在下載未完成的文件(不緩存下載請求)
    //resumableDownloadPath是斷點下載路徑,如果該路徑不為空,說明有未完成的下載任務(wù),則直接發(fā)送請求繼續(xù)下載。
    if (self.resumableDownloadPath) {
        //調(diào)用無緩存請求方法
        [self startWithoutCache];
        return;
    }

    //獲取緩存失敗
    //loadCacheWithError:方法驗證了加載緩存是否成功的方法(返回值為YES,說明可以加載緩存;反之亦然
    if (![self loadCacheWithError:nil]) {
        //調(diào)用無緩存請求方法
        [self startWithoutCache];
        return;
    }

    //到這里一定是有緩存
    _dataFromCache = YES;

    //開啟主線程異步方法
    dispatch_async(dispatch_get_main_queue(), ^{
        //緩存處理
        [self requestCompletePreprocessor];
        //用戶可以在這里進(jìn)行真正的回調(diào)前操作
        [self requestCompleteFilter];
        
        //執(zhí)行回調(diào)
        YTKRequest *strongSelf = self;
        //請求完成后的代理回調(diào)
        [strongSelf.delegate requestFinished:strongSelf];
        //請求成功的block
        if (strongSelf.successCompletionBlock) {
            strongSelf.successCompletionBlock(strongSelf);
        }
        //手動把成功和失敗block都置為nil,避免循環(huán)引用
        [strongSelf clearCompletionBlock];
    });
}

二.判斷有緩存情況還是無緩存情況

loadCacheWithError

驗證加載緩存是否成功的方法(返回值為YES,說明可以加載緩存;反之亦然),看一下具體實現(xiàn):

- (BOOL)loadCacheWithError:(NSError * _Nullable __autoreleasing *)error {
    //緩存時間小于0,緩存不可用,返回NO(緩存時間默認(rèn)為-1,需要用戶手動設(shè)置,單位是秒)
    if ([self cacheTimeInSeconds] < 0) {
        if (error) {
            *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheTime userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache time"}];
        }
        return NO;
    }

    //是否有緩存的元數(shù)據(jù),如果沒有,返回錯誤
    //元數(shù)據(jù)是指數(shù)據(jù)的數(shù)據(jù),在這里描述了緩存數(shù)據(jù)本身的一些特征:包括版本號,緩存時間,敏感信息等等
    if (![self loadCacheMetadata]) {
        if (error) {
            *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidMetadata userInfo:@{ NSLocalizedDescriptionKey:@"Invalid metadata. Cache may not exist"}];
        }
        return NO;
    }

    //檢查緩存是否可用,如果不可用,返回NO
    if (![self validateCacheWithError:error]) {
        return NO;
    }

    //檢查緩存是否可以加載,如果不能加載,返回NO
    if (![self loadCacheData]) {
        if (error) {
            *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorInvalidCacheData userInfo:@{ NSLocalizedDescriptionKey:@"Invalid cache data"}];
        }
        return NO;
    }

    //其他情況表示能夠加載緩存
    return YES;
}
loadCacheMetadata

我們來看一下上面關(guān)于緩存的元數(shù)據(jù)的獲取方法。

- (BOOL)loadCacheMetadata {
    //獲取緩存元數(shù)據(jù)路徑
    NSString *path = [self cacheMetadataFilePath];
    NSFileManager * fileManager = [NSFileManager defaultManager];
    //使用try catch捕獲異常避免崩潰
    if ([fileManager fileExistsAtPath:path isDirectory:nil]) {
        @try {
            _cacheMetadata = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
            return YES;
        } @catch (NSException *exception) {
            YTKLog(@"Load cache metadata failed, reason = %@", exception.reason);
            return NO;
        }
    }
    return NO;
}

其中 [self cacheMetadataFilePath] 緩存元數(shù)據(jù)路徑獲取。
緩存元數(shù)據(jù)路徑由根路徑加緩存元數(shù)據(jù)子路徑構(gòu)成。

  • 根路徑:默認(rèn)為 library 路徑拼接 "LazyRequestCache" 。如果遵循 YTKCacheDirPathFilterProtocol 協(xié)議實現(xiàn) - (NSString *)filterCacheDirPath:(NSString *)originPath withRequest:(YTKBaseRequest *)request; 方法可更改跟路徑。根據(jù)路徑查詢目錄,如果目錄不存在則創(chuàng)建。
  • 緩存元數(shù)據(jù)子路徑: 獲取 requestMethodbaseUrl、requestUrl、argument 組成requestInfo,加密后形成緩存文件名。再加上.metadata形成緩存元數(shù)據(jù)子路徑。
cacheMetadata(YTKCacheMetadata)

它描述的是緩存的版本號,敏感信息,創(chuàng)建時間,app版本等信息,并支持序列化處理,可以保存在磁盤里。
因此,loadCacheMetadata 方法的目的是將之前被序列化保存的緩存元數(shù)據(jù)信息反序列化,賦給自身的cacheMetadata 屬性上。

//YTKRequest.m
@interface YTKCacheMetadata : NSObject<NSSecureCoding>

@property (nonatomic, assign) long long version;
@property (nonatomic, strong) NSString *sensitiveDataString;
@property (nonatomic, assign) NSStringEncoding stringEncoding;
@property (nonatomic, strong) NSDate *creationDate;
@property (nonatomic, strong) NSString *appVersionString;

@end
validateCacheWithError

逐一驗證元數(shù)據(jù)里的各項信息是否符合要求。

- (BOOL)validateCacheWithError:(NSError * _Nullable __autoreleasing *)error {
    // 是否大于過期時間
    NSDate *creationDate = self.cacheMetadata.creationDate;
    NSTimeInterval duration = -[creationDate timeIntervalSinceNow];
    //[self cacheTimeInSeconds] 設(shè)定緩存有效時間
    if (duration < 0 || duration > [self cacheTimeInSeconds]) {
        if (error) {
            *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorExpired userInfo:@{ NSLocalizedDescriptionKey:@"Cache expired"}];
        }
        return NO;
    }
    // 緩存的版本號是否符合
    long long cacheVersionFileContent = self.cacheMetadata.version;
    if (cacheVersionFileContent != [self cacheVersion]) {
        if (error) {
            *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorVersionMismatch userInfo:@{ NSLocalizedDescriptionKey:@"Cache version mismatch"}];
        }
        return NO;
    }
    // 敏感信息是否符合 (元數(shù)據(jù)中的敏感信息 與 請求中設(shè)定的敏感信息 比對)
    NSString *sensitiveDataString = self.cacheMetadata.sensitiveDataString;
    NSString *currentSensitiveDataString = ((NSObject *)[self cacheSensitiveData]).description;
    if (sensitiveDataString || currentSensitiveDataString) {
        // If one of the strings is nil, short-circuit evaluation will trigger
        if (sensitiveDataString.length != currentSensitiveDataString.length || ![sensitiveDataString isEqualToString:currentSensitiveDataString]) {
            if (error) {
                *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorSensitiveDataMismatch userInfo:@{ NSLocalizedDescriptionKey:@"Cache sensitive data mismatch"}];
            }
            return NO;
        }
    }
    // app的版本是否符合
    NSString *appVersionString = self.cacheMetadata.appVersionString;
    NSString *currentAppVersionString = [YTKNetworkUtils appVersionString];
    if (appVersionString || currentAppVersionString) {
        if (appVersionString.length != currentAppVersionString.length || ![appVersionString isEqualToString:currentAppVersionString]) {
            if (error) {
                *error = [NSError errorWithDomain:YTKRequestCacheErrorDomain code:YTKRequestCacheErrorAppVersionMismatch userInfo:@{ NSLocalizedDescriptionKey:@"App version mismatch"}];
            }
            return NO;
        }
    }
    return YES;
}
loadCacheData
- (BOOL)loadCacheData {
    // 獲取緩存路徑
    NSString *path = [self cacheFilePath];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error = nil;

    // 如果緩存目錄存在
    if ([fileManager fileExistsAtPath:path isDirectory:nil]) {
        NSData *data = [NSData dataWithContentsOfFile:path];
        _cacheData = data;
        _cacheString = [[NSString alloc] initWithData:_cacheData encoding:self.cacheMetadata.stringEncoding];
        // 根據(jù)數(shù)據(jù)類型對緩存數(shù)據(jù)進(jìn)行處理
        switch (self.responseSerializerType) {
            case YTKResponseSerializerTypeHTTP:
                // Do nothing.
                return YES;
            case YTKResponseSerializerTypeJSON:
                _cacheJSON = [NSJSONSerialization JSONObjectWithData:_cacheData options:(NSJSONReadingOptions)0 error:&error];
                return error == nil;
            case YTKResponseSerializerTypeXMLParser:
                _cacheXML = [[NSXMLParser alloc] initWithData:_cacheData];
                return YES;
        }
    }
    return NO;
}
dataFromCache

當(dāng)確認(rèn)緩存可以成功取出后,手動設(shè)置 dataFromCache 屬性為 YES,說明當(dāng)前的請求結(jié)果是來自于緩存,而沒有通過網(wǎng)絡(luò)請求。

requestCompletePreprocessor
- (void)requestCompletePreprocessor {
    [super requestCompletePreprocessor];

    // 是否異步將responseData寫入緩存(寫入緩存的任務(wù)放在專門的隊列ytkrequest_cache_writing_queue進(jìn)行)
    if (self.writeCacheAsynchronously) {
        dispatch_async(ytkrequest_cache_writing_queue(), ^{
            // 保存響應(yīng)數(shù)據(jù)到緩存
            [self saveResponseDataToCacheFile:[super responseData]];
        });
    } else {
        // 保存響應(yīng)數(shù)據(jù)到緩存
        [self saveResponseDataToCacheFile:[super responseData]];
    }
}
- (void)saveResponseDataToCacheFile:(NSData *)data {
    // 設(shè)置緩存時間大于0 且 此數(shù)據(jù)之前不是緩存數(shù)據(jù)
    if ([self cacheTimeInSeconds] > 0 && ![self isDataFromCache]) {
        // 數(shù)據(jù)額非空
        if (data != nil) {
            @try {
                // 總是覆蓋舊數(shù)據(jù)
                [data writeToFile:[self cacheFilePath] atomically:YES];

                // 設(shè)置緩存元數(shù)據(jù)
                YTKCacheMetadata *metadata = [[YTKCacheMetadata alloc] init];
                metadata.version = [self cacheVersion];
                metadata.sensitiveDataString = ((NSObject *)[self cacheSensitiveData]).description;
                metadata.stringEncoding = [YTKNetworkUtils stringEncodingWithRequest:self];
                metadata.creationDate = [NSDate date];
                metadata.appVersionString = [YTKNetworkUtils appVersionString];
                // 儲存緩存元數(shù)據(jù)
                [NSKeyedArchiver archiveRootObject:metadata toFile:[self cacheMetadataFilePath]];
            } @catch (NSException *exception) {
                YTKLog(@"Save cache failed, reason = %@", exception.reason);
            }
        }
    }
}
requestCompleteFilter
- (void)requestCompleteFilter {
}

可以看到此方法內(nèi)容為空。用戶可以在這里做一些回調(diào)前的處理。

三.緩存情況下

    //到這里一定是有緩存
    _dataFromCache = YES;

    //開啟主線程異步方法
    dispatch_async(dispatch_get_main_queue(), ^{
        //緩存處理
        [self requestCompletePreprocessor];
        //用戶可以在這里進(jìn)行真正的回調(diào)前操作
        [self requestCompleteFilter];
        
        //執(zhí)行回調(diào)
        YTKRequest *strongSelf = self;
        //請求完成后的代理回調(diào)
        [strongSelf.delegate requestFinished:strongSelf];
        //請求成功的block
        if (strongSelf.successCompletionBlock) {
            strongSelf.successCompletionBlock(strongSelf);
        }
        //手動把成功和失敗block都置為nil,避免循環(huán)引用
        [strongSelf clearCompletionBlock];
    });

在有緩存的情況下,直接利用緩存執(zhí)行代理和 block 的回調(diào)

四.無緩存情況下

執(zhí)行無緩存請求。

 [self startWithoutCache];
startWithoutCache
- (void)startWithoutCache {
    // 清除本類中全局變量
    [self clearCacheVariables];
    //調(diào)用父類的 start 方法
    [super start];
}
- (void)clearCacheVariables {
    _cacheData = nil;
    _cacheXML = nil;
    _cacheJSON = nil;
    _cacheString = nil;
    _cacheMetadata = nil;
    _dataFromCache = NO;
}
YTKBaseRequest start 方法
- (void)start {
    // 告訴Accessories即將回調(diào)了(其實是即將發(fā)起請求)
    [self toggleAccessoriesWillStartCallBack];
    // Agent添加請求
    [[YTKNetworkAgent sharedAgent] addRequest:self];
}

- (void)toggleAccessoriesWillStartCallBack {
    // 遵循YTKRequestAccessory代理的類執(zhí)行start方法
    for (id<YTKRequestAccessory> accessory in self.requestAccessories) {
        if ([accessory respondsToSelector:@selector(requestWillStart:)]) {
            [accessory requestWillStart:self];
        }
    }
}
addRequest
- (void)addRequest:(YTKBaseRequest *)request {
    
    // 斷言request不為空
    NSParameterAssert(request != nil);

    // 定義error默認(rèn)為空
    NSError * __autoreleasing requestSerializationError = nil;

    // 獲取自定義request,默認(rèn)為空 如果自定義子類中中實現(xiàn)buildCustomUrlRequest方法則獲取方法中的request
    NSURLRequest *customUrlRequest= [request buildCustomUrlRequest];
    // 判斷自定義request是否存在
    if (customUrlRequest) {
        __block NSURLSessionDataTask *dataTask = nil;
        // 調(diào)用afn中網(wǎng)絡(luò)請求方法
        dataTask = [_manager dataTaskWithRequest:customUrlRequest completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
            // 使用handleRequestResult處理afn返回結(jié)果
            [self handleRequestResult:dataTask responseObject:responseObject error:error];
        }];
        request.requestTask = dataTask;
    } else {
        // 如果自定義request不存在,使用傳入的request(即baseRequest類型的具體request)
        request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError];
    }

    // 如果請求序列化錯誤
    if (requestSerializationError) {
        // 處理錯誤
        [self requestDidFailWithRequest:request error:requestSerializationError];
        return;
    }

    // 斷言
    NSAssert(request.requestTask != nil, @"requestTask should not be nil");

    // ios8 以后可以設(shè)定請求優(yōu)先級
    if ([request.requestTask respondsToSelector:@selector(priority)]) {
        switch (request.requestPriority) {
            case YTKRequestPriorityHigh:
                request.requestTask.priority = NSURLSessionTaskPriorityHigh;
                break;
            case YTKRequestPriorityLow:
                request.requestTask.priority = NSURLSessionTaskPriorityLow;
                break;
            case YTKRequestPriorityDefault:
                /*!!fall through*/
            default:
                request.requestTask.priority = NSURLSessionTaskPriorityDefault;
                break;
        }
    }

    YTKLog(@"Add request: %@", NSStringFromClass([request class]));
    //請求備份
    [self addRequestToRecord:request];
    // 開始請求
    [request.requestTask resume];
}

此方法主要分三個部分:

  • 第一部分是獲取當(dāng)前請求對應(yīng)的task并賦給request的requestTask屬性
  • 第二部分是把request放入專門用來保存請求的字典中
  • 第三部分是啟動task
sessionTaskForRequest: error :
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
    // 獲取請求方法類型
    YTKRequestMethod method = [request requestMethod];
    // 獲取url
    NSString *url = [self buildRequestUrl:request];
    // 獲取請求參數(shù)
    id param = request.requestArgument;
    AFConstructingBlock constructingBlock = [request constructingBodyBlock];
    // 獲取request serializer
    AFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request];

    // 根據(jù)不同請求方法類型返回task
    switch (method) {
        case YTKRequestMethodGET:
            // 如果斷點下載
            if (request.resumableDownloadPath) {
                // 返回下載任務(wù)
                return [self downloadTaskWithDownloadPath:request.resumableDownloadPath requestSerializer:requestSerializer URLString:url parameters:param progress:request.resumableDownloadProgressBlock error:error];
            } else {
                // 返回普通get任務(wù)
                return [self dataTaskWithHTTPMethod:@"GET" requestSerializer:requestSerializer URLString:url parameters:param error:error];
            }
        case YTKRequestMethodPOST:
            //返回post任務(wù)
            return [self dataTaskWithHTTPMethod:@"POST" requestSerializer:requestSerializer URLString:url parameters:param constructingBodyWithBlock:constructingBlock error:error];
        case YTKRequestMethodHEAD:
            return [self dataTaskWithHTTPMethod:@"HEAD" requestSerializer:requestSerializer URLString:url parameters:param error:error];
        case YTKRequestMethodPUT:
            return [self dataTaskWithHTTPMethod:@"PUT" requestSerializer:requestSerializer URLString:url parameters:param error:error];
        case YTKRequestMethodDELETE:
            return [self dataTaskWithHTTPMethod:@"DELETE" requestSerializer:requestSerializer URLString:url parameters:param error:error];
        case YTKRequestMethodPATCH:
            return [self dataTaskWithHTTPMethod:@"PATCH" requestSerializer:requestSerializer URLString:url parameters:param error:error];
    }
}

五. NSURLSessionTask 參數(shù)配置

URL處理
//YTKNetworkAgent.m
- (NSURLSessionTask *)sessionTaskForRequest:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {

  ...
  NSString *url = [self buildRequestUrl:request];
  ...

}

- (NSString *)buildRequestUrl:(YTKBaseRequest *)request {
    // 斷言
    NSParameterAssert(request != nil);

    //獲取子類中自定義requesturl
    NSString *detailUrl = [request requestUrl];
    NSURL *temp = [NSURL URLWithString:detailUrl];
    
    // 存在host和scheme的url立即返回正確
    if (temp && temp.host && temp.scheme) {
        return detailUrl;
    }
    
    // 如果遵循YTKUrlFilterProtocol 重寫filterUrl方法 過濾url
    NSArray *filters = [_config urlFilters];
    for (id<YTKUrlFilterProtocol> f in filters) {
        detailUrl = [f filterUrl:detailUrl withRequest:request];
    }

    
    NSString *baseUrl;
    if ([request useCDN]) {
        // 如果使用CDN,在當(dāng)前請求沒有配置CDN地址的情況下,返回全局配置的CDN
        if ([request cdnUrl].length > 0) {
            baseUrl = [request cdnUrl];
        } else {
            baseUrl = [_config cdnUrl];
        }
    } else {
        // 如果使用baseUrl,在當(dāng)前請求沒有配置baseUrl,返回全局配置的baseUrl
        if ([request baseUrl].length > 0) {
            baseUrl = [request baseUrl];
        } else {
            baseUrl = [_config baseUrl];
        }
    }
    // 如果末尾沒有/,則在末尾添加一個/
    NSURL *url = [NSURL URLWithString:baseUrl];

    if (baseUrl.length > 0 && ![baseUrl hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }

    // 拼接baseUrl和detailUrl并返回完整url
    return [NSURL URLWithString:detailUrl relativeToURL:url].absoluteString;
}
requestSerializerForRequest
- (AFHTTPRequestSerializer *)requestSerializerForRequest:(YTKBaseRequest *)request {
    AFHTTPRequestSerializer *requestSerializer = nil;
    // 判斷請求的序列化類型是HTTP還是JSON (可通過requestSerializerType手動設(shè)定)
    if (request.requestSerializerType == YTKRequestSerializerTypeHTTP) {
        // 創(chuàng)建HTTP類型
        requestSerializer = [AFHTTPRequestSerializer serializer];
    } else if (request.requestSerializerType == YTKRequestSerializerTypeJSON) {
        // 創(chuàng)建JSON類型
        requestSerializer = [AFJSONRequestSerializer serializer];
    }

    // 超時時間 默認(rèn)60 可手動設(shè)置
    requestSerializer.timeoutInterval = [request requestTimeoutInterval];
    // 是否允許使用蜂窩移動數(shù)據(jù)(4g)默認(rèn)可以 可手動設(shè)置
    requestSerializer.allowsCellularAccess = [request allowsCellularAccess];

    // 如果當(dāng)前請求需要服務(wù)器賬號和密碼
    NSArray<NSString *> *authorizationHeaderFieldArray = [request requestAuthorizationHeaderFieldArray];
    if (authorizationHeaderFieldArray != nil) {
        [requestSerializer setAuthorizationHeaderFieldWithUsername:authorizationHeaderFieldArray.firstObject
                                                          password:authorizationHeaderFieldArray.lastObject];
    }

    // 如果當(dāng)前請求需要自定義 HTTPHeaderField
    NSDictionary<NSString *, NSString *> *headerFieldValueDictionary = [request requestHeaderFieldValueDictionary];
    if (headerFieldValueDictionary != nil) {
        for (NSString *httpHeaderField in headerFieldValueDictionary.allKeys) {
            NSString *value = headerFieldValueDictionary[httpHeaderField];
            [requestSerializer setValue:value forHTTPHeaderField:httpHeaderField];
        }
    }
    return requestSerializer;
}

六.開始請求

獲取 task
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                               requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                       constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
                                           error:(NSError * _Nullable __autoreleasing *)error {
    NSMutableURLRequest *request = nil;

    // 根據(jù)有無Block使用AFN中的AFHTTPRequestSerializer創(chuàng)建request
    if (block) {
        request = [requestSerializer multipartFormRequestWithMethod:method URLString:URLString parameters:parameters constructingBodyWithBlock:block error:error];
    } else {
        request = [requestSerializer requestWithMethod:method URLString:URLString parameters:parameters error:error];
    }

    // 根據(jù)request創(chuàng)建dataTask
    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [_manager dataTaskWithRequest:request
                           completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *_error) {
                               // 響應(yīng)的統(tǒng)一處理
                               [self handleRequestResult:dataTask responseObject:responseObject error:_error];
                           }];

    return dataTask;
}
啟動task
[request.requestTask resume];

七.處理回調(diào)

handleRequestResult

響應(yīng)的統(tǒng)一處理內(nèi)部操作

- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
    // 根據(jù)task獲取request
    Lock();
    YTKBaseRequest *request = _requestsRecord[@(task.taskIdentifier)];
    Unlock();

    // When the request is cancelled and removed from records, the underlying
    // AFNetworking failure callback will still kicks in, resulting in a nil `request`.
    //
    // Here we choose to completely ignore cancelled tasks. Neither success or failure
    // callback will be called.
    if (!request) {
        return;
    }

    YTKLog(@"Finished Request: %@", NSStringFromClass([request class]));

    NSError * __autoreleasing serializationError = nil;
    NSError * __autoreleasing validationError = nil;

    NSError *requestError = nil;
    BOOL succeed = NO;

    // 獲取request對應(yīng)的response
    request.responseObject = responseObject;
    if ([request.responseObject isKindOfClass:[NSData class]]) {
        // 獲取 responseData
        request.responseData = responseObject;
        // 獲取responseString
        request.responseString = [[NSString alloc] initWithData:responseObject encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];

        // 根據(jù)返回的響應(yīng)的序列化的類型來得到對應(yīng)類型的響應(yīng)
        switch (request.responseSerializerType) {
            case YTKResponseSerializerTypeHTTP:
                // Default serializer. Do nothing.
                break;
            case YTKResponseSerializerTypeJSON:
                request.responseObject = [self.jsonResponseSerializer responseObjectForResponse:task.response data:request.responseData error:&serializationError];
                request.responseJSONObject = request.responseObject;
                break;
            case YTKResponseSerializerTypeXMLParser:
                request.responseObject = [self.xmlParserResponseSerialzier responseObjectForResponse:task.response data:request.responseData error:&serializationError];
                break;
        }
    }
    
    // 判斷是否有錯誤,將錯誤對象賦值給requestError,改變succeed的布爾值。目的是根據(jù)succeed的值來判斷到底是進(jìn)行成功的回調(diào)還是失敗的回調(diào)
    if (error) {
        // 如果該方法傳入的error不為nil
        succeed = NO;
        requestError = error;
    } else if (serializationError) {
        // 如果序列化失敗了
        succeed = NO;
        requestError = serializationError;
    } else {
        // 即使沒有error而且序列化通過,也要驗證request是否有效
        succeed = [self validateResult:request error:&validationError];
        requestError = validationError;
    }

    // 根據(jù)succeed的布爾值來調(diào)用相應(yīng)的處理
    if (succeed) {
        // 請求成功的處理
        [self requestDidSucceedWithRequest:request];
    } else {
        // 請求失敗的處理
        [self requestDidFailWithRequest:request error:requestError];
    }

    // 回調(diào)完成的處理
    dispatch_async(dispatch_get_main_queue(), ^{
        // 在字典里移除當(dāng)前request
        [self removeRequestFromRecord:request];
        // 清除所有block
        [request clearCompletionBlock];
    });
}

簡單講解一下上面的代碼:

  • 前面以 task 的 identifier 為 key,將 request 存入字典,這里根據(jù) task 取出 request。
  • 然后將獲得的responseObject進(jìn)行處理,將處理后獲得的responseObject,responseData和responseString賦值給當(dāng)前的請求實例request。
  • 再根據(jù)這些值的獲取情況來判斷最終回調(diào)的成?。ǜ淖僺ucceed的值)。
  • 最后根據(jù)succeed的值來進(jìn)行成功和失敗的回調(diào)。
validateResult

驗證返回的json數(shù)據(jù)是否有效

- (BOOL)validateResult:(YTKBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
    // 判斷statusCode是否在200~299之間,如果是則返回狀態(tài)有效
    BOOL result = [request statusCodeValidator];
    if (!result) {
        // 如果狀態(tài)碼無效,傳入error地址存在
        if (error) {
            // 創(chuàng)建NSError對象賦值給error響應(yīng)地址
            *error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidStatusCode userInfo:@{NSLocalizedDescriptionKey:@"Invalid status code"}];
        }
        // 返回result
        return result;
    }
    
    // 狀態(tài)碼有效的情況下判斷json是否有效
    // 去返回json
    id json = [request responseJSONObject];
    // 取設(shè)定的json數(shù)據(jù)格式,可手動設(shè)置
    id validator = [request jsonValidator];
    // 如果同時存在
    if (json && validator) {
        // 判斷json是否符合設(shè)定的jsonValidator
        result = [YTKNetworkUtils validateJSON:json withValidator:validator];
        if (!result) {
            if (error) {
                *error = [NSError errorWithDomain:YTKRequestValidationErrorDomain code:YTKRequestValidationErrorInvalidJSONFormat userInfo:@{NSLocalizedDescriptionKey:@"Invalid JSON format"}];
            }
            return result;
        }
    }
    return YES;
}

在這里,首先,用statusCodeValidator方法判斷響應(yīng)的code是否在正確的范圍,然后再判斷json的有效性。

validateJSON

判斷目標(biāo)字典對象是否滿足于設(shè)置字典模型的key一致時,value類型與自定義類型格式相同

+ (BOOL)validateJSON:(id)json withValidator:(id)jsonValidator {
    // 判斷是否同為字典類型
    if ([json isKindOfClass:[NSDictionary class]] &&
        [jsonValidator isKindOfClass:[NSDictionary class]]) {
        // 轉(zhuǎn)為字典類型
        NSDictionary * dict = json;
        NSDictionary * validator = jsonValidator;
        BOOL result = YES;
        // 實例遍歷類型對象
        NSEnumerator * enumerator = [validator keyEnumerator];
        NSString * key;
        // 使用while遍歷validator的key數(shù)組,直到為空
        while ((key = [enumerator nextObject]) != nil) {
            // 根據(jù)key取dict字典value,和validator字典中定義類型
            id value = dict[key];
            id format = validator[key];
            // 如果vlue仍為字典或者數(shù)組,遞歸
            if ([value isKindOfClass:[NSDictionary class]]
                || [value isKindOfClass:[NSArray class]]) {
                result = [self validateJSON:value withValidator:format];
                if (!result) {
                    break;
                }
            } else {
                // 如果value不為定義類型 且 value不為空類型
                if ([value isKindOfClass:format] == NO &&
                    [value isKindOfClass:[NSNull class]] == NO) {
                    // 結(jié)果為NO
                    result = NO;
                    // 有一個結(jié)果為NO則全部為NO, 不需要再循環(huán),跳出
                    break;
                }
            }
        }
        // 返回結(jié)果
        return result;
    // 如果json和jsonValidator同為數(shù)組類型
    } else if ([json isKindOfClass:[NSArray class]] &&
               [jsonValidator isKindOfClass:[NSArray class]]) {
        NSArray * validatorArray = (NSArray *)jsonValidator;
        if (validatorArray.count > 0) {
            // 目標(biāo)對象轉(zhuǎn)為數(shù)組
            NSArray * array = json;
            // 驗證對象數(shù)組取第一個字典為模型即可
            NSDictionary * validator = jsonValidator[0];
            // 目標(biāo)對象中的每一個格式 和 字典模型一致即可
            for (id item in array) {
                // 遞歸
                BOOL result = [self validateJSON:item withValidator:validator];
                if (!result) {
                    return NO;
                }
            }
        }
        return YES;
    // 類型直接相同返回yes
    } else if ([json isKindOfClass:jsonValidator]) {
        return YES;
    // 類型不同返回no
    } else {
        return NO;
    }
}
requestDidSucceedWithRequest

請求成功的處理。

- (void)requestDidSucceedWithRequest:(YTKBaseRequest *)request {
    // 加入手動創(chuàng)建的自動釋放池中,執(zhí)行完池中方法后相應(yīng)內(nèi)存直接釋放
    // 響應(yīng)數(shù)據(jù)寫入文件緩存后相應(yīng)內(nèi)存直接釋放,最大限度降低內(nèi)存
    @autoreleasepool {
        // 寫入緩存
        [request requestCompletePreprocessor];
    }
    
    // 異步回到主隊列
    dispatch_async(dispatch_get_main_queue(), ^{
        // 告訴Accessories請求就要結(jié)束了
        [request toggleAccessoriesWillStopCallBack];
        // 在最后的回調(diào)之前可以做的處理,用戶可以自定義
        [request requestCompleteFilter];

        // 如果有代理,則調(diào)用成功的代理
        if (request.delegate != nil) {
            [request.delegate requestFinished:request];
        }
        
        // 如果傳入了成功的block,則調(diào)用
        if (request.successCompletionBlock) {
            request.successCompletionBlock(request);
        }
        
        // 告訴Accessories請求已經(jīng)結(jié)束了
        [request toggleAccessoriesDidStopCallBack];
    });
}
requestDidFailWithRequest

請求失敗處理。

- (void)requestDidFailWithRequest:(YTKBaseRequest *)request error:(NSError *)error {
    // 賦值error
    request.error = error;
    YTKLog(@"Request %@ failed, status code = %ld, error = %@",
           NSStringFromClass([request class]), (long)request.responseStatusCode, error.localizedDescription);

    // 儲存未完成的下載數(shù)據(jù),存入resumableDownloadPath,等待斷點續(xù)傳
    NSData *incompleteDownloadData = error.userInfo[NSURLSessionDownloadTaskResumeData];
    if (incompleteDownloadData) {
        [incompleteDownloadData writeToURL:[self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath] atomically:YES];
    }

    // 下載失敗清除文件目錄中的文件并清空響應(yīng)
    if ([request.responseObject isKindOfClass:[NSURL class]]) {
        NSURL *url = request.responseObject;
        if (url.isFileURL && [[NSFileManager defaultManager] fileExistsAtPath:url.path]) {
            request.responseData = [NSData dataWithContentsOfURL:url];
            request.responseString = [[NSString alloc] initWithData:request.responseData encoding:[YTKNetworkUtils stringEncodingWithRequest:request]];

            [[NSFileManager defaultManager] removeItemAtURL:url error:nil];
        }
        request.responseObject = nil;
    }

    // 失敗預(yù)處理默認(rèn)是不做處理的,如需要需自定義
    @autoreleasepool {
        [request requestFailedPreprocessor];
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        // 告訴Accessories請求就要結(jié)束了
        [request toggleAccessoriesWillStopCallBack];
        // 在真正的回調(diào)之前做的處理,可自定義
        [request requestFailedFilter];

        // 如果有代理,就調(diào)用代理
        if (request.delegate != nil) {
            [request.delegate requestFailed:request];
        }
        // 如果傳入了失敗回調(diào)的block代碼,就調(diào)用block
        if (request.failureCompletionBlock) {
            request.failureCompletionBlock(request);
        }
        // 告訴Accessories請求已經(jīng)停止了
        [request toggleAccessoriesDidStopCallBack];
    });
}

在這個方法里,首先判斷了當(dāng)前任務(wù)是否為下載任務(wù),如果是,則儲存當(dāng)前已經(jīng)下載好的data到resumableDownloadPath里面。而如果下載任務(wù)失敗,則將其對應(yīng)的在本地保存的路徑上的文件清空。


流程圖.png

八.取消請求

兩個取消方法:

//YTKNetworkAgent.h
///  取消某個request
- (void)cancelRequest:(YTKBaseRequest *)request;

///  取消所有添加的request
- (void)cancelAllRequests;
cancelRequest

取消某個request。

- (void)cancelRequest:(YTKBaseRequest *)request {
    // 斷言request不為空
    NSParameterAssert(request != nil);

    // 如果下載過程中取消
    if (request.resumableDownloadPath) {
        NSURLSessionDownloadTask *requestTask = (NSURLSessionDownloadTask *)request.requestTask;
        // 寫入resumableDownloadPath等待斷點續(xù)傳。方法內(nèi)部會調(diào)用cancel方法
        [requestTask cancelByProducingResumeData:^(NSData *resumeData) {
            NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
            [resumeData writeToURL:localUrl atomically:YES];
        }];
    } else {
        // 獲取request的task,并取消
        [request.requestTask cancel];
    }

    // 從字典里移除當(dāng)前request
    [self removeRequestFromRecord:request];
    // 清理所有block
    [request clearCompletionBlock];
}

- (void)removeRequestFromRecord:(YTKBaseRequest *)request {
    // 加鎖
    Lock();
    // 移除字典中這組數(shù)據(jù)
    [_requestsRecord removeObjectForKey:@(request.requestTask.taskIdentifier)];
    YTKLog(@"Request queue size = %zd", [_requestsRecord count]);
    Unlock();
}
cancelAllRequests

取消所有request。

- (void)cancelAllRequests {
    // 加鎖。多個線程對同一個字典進(jìn)行操作的,都需要加鎖
    Lock();
    NSArray *allKeys = [_requestsRecord allKeys];
    Unlock();
    if (allKeys && allKeys.count > 0) {
        // copy
        NSArray *copiedKeys = [allKeys copy];
        for (NSNumber *key in copiedKeys) {
            Lock();
            YTKBaseRequest *request = _requestsRecord[key];
            Unlock();
            // We are using non-recursive lock.
            // Do not lock `stop`, otherwise deadlock may occur.
            // stop每個請求
            [request stop];
        }
    }
}
stop
- (void)stop {
    // 告訴Accessories將要停止回調(diào)了
    [self toggleAccessoriesWillStopCallBack];
    // 清空代理
    self.delegate = nil;
    // 調(diào)用agent的取消某個request的方法
    [[YTKNetworkAgent sharedAgent] cancelRequest:self];
    // 告訴Accessories已經(jīng)停止回調(diào)完成了
    [self toggleAccessoriesDidStopCallBack];
}

九.批量請求

初始化
- (instancetype)initWithRequestArray:(NSArray<YTKRequest *> *)requestArray {
    self = [super init];
    if (self) {
        // 保存為屬性,使用copy即使requestArray改變_requestArray也不會變
        _requestArray = [requestArray copy];
        //批量請求完成的數(shù)量初始化為0
        _finishedCount = 0;
        //類型檢查,所有元素都必須為YTKRequest或的它的子類,否則強(qiáng)制初始化失敗
        for (YTKRequest * req in _requestArray) {
            if (![req isKindOfClass:[YTKRequest class]]) {
                YTKLog(@"Error, request item must be YTKRequest instance.");
                return nil;
            }
        }
    }
    return self;
}

初始化以后,我們就可以調(diào)用start方法來發(fā)起當(dāng)前YTKBatchRequest實例所管理的所有請求了。

- (void)start {
    //如果batch里第一個請求已經(jīng)成功結(jié)束,則不能再start
    if (_finishedCount > 0) {
        YTKLog(@"Error! Batch request has already started.");
        return;
    }
    //最開始設(shè)定失敗的request為nil
    _failedRequest = nil;
    //使用YTKBatchRequestAgent來管理當(dāng)前的批量請求
    [[YTKBatchRequestAgent sharedAgent] addBatchRequest:self];
    [self toggleAccessoriesWillStartCallBack];
    
    //遍歷所有request,并開始請求
    for (YTKRequest * req in _requestArray) {
        req.delegate = self;
        [req clearCompletionBlock];
        [req start];
    }
}
requestFinished

單個請求完成,如果全部完成則批量請求完成。

- (void)requestFinished:(YTKRequest *)request {
    //某個request成功后,首先讓_finishedCount + 1
    _finishedCount++;
    //如果_finishedCount等于_requestArray的個數(shù),則判定當(dāng)前batch請求成功
    if (_finishedCount == _requestArray.count) {
        //調(diào)用即將結(jié)束的代理
        [self toggleAccessoriesWillStopCallBack];
        //調(diào)用請求成功的代理
        if ([_delegate respondsToSelector:@selector(batchRequestFinished:)]) {
            [_delegate batchRequestFinished:self];
        }
        //調(diào)用批量請求成功的block
        if (_successCompletionBlock) {
            _successCompletionBlock(self);
        }
        //清空成功和失敗的block
        [self clearCompletionBlock];
        //調(diào)用請求結(jié)束的代理
        [self toggleAccessoriesDidStopCallBack];
         //從YTKBatchRequestAgent里移除當(dāng)前的batch
        [[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
    }
}
requestFailed

單個請求失敗即批量請求失敗,將失敗的請求保存到_failedRequest中

- (void)requestFailed:(YTKRequest *)request {
    // 失敗請求保存在_failedRequest中
    _failedRequest = request;
    // 調(diào)用即將結(jié)束的代理
    [self toggleAccessoriesWillStopCallBack];
    // 停止batch里所有的請求
    for (YTKRequest *req in _requestArray) {
        [req stop];
    }
    // 調(diào)用批量請求失敗的代理
    if ([_delegate respondsToSelector:@selector(batchRequestFailed:)]) {
        [_delegate batchRequestFailed:self];
    }
    // 調(diào)用請求失敗的block
    if (_failureCompletionBlock) {
        _failureCompletionBlock(self);
    }
    // 清空成功和失敗的block
    [self clearCompletionBlock];
    // 調(diào)用請求結(jié)束的代理
    [self toggleAccessoriesDidStopCallBack];
    // 從YTKBatchRequestAgent里移除當(dāng)前的batch
    [[YTKBatchRequestAgent sharedAgent] removeBatchRequest:self];
}

十.鏈?zhǔn)秸埱?/h4>

和批量請求類似,處理鏈?zhǔn)秸埱蟮念愂荵TKChainRequest,并且用YTKChainRequestAgent單例來管理YTKChainRequest的實例

init

初始化方法。

- (instancetype)init {
    self = [super init];
    if (self) {
        // 下一個請求的index
        _nextRequestIndex = 0;
        // 保存鏈?zhǔn)秸埱蟮臄?shù)組
        _requestArray = [NSMutableArray array];
        // 保存回調(diào)的數(shù)組
        _requestCallbackArray = [NSMutableArray array];
        // 空回調(diào),用來填充用戶沒有定義的回調(diào)block
        _emptyCallback = ^(YTKChainRequest *chainRequest, YTKBaseRequest *baseRequest) {
            // do nothing
        };
    }
    return self;
}
addRequest

添加request的接口。

- (void)addRequest:(YTKBaseRequest *)request callback:(YTKChainCallback)callback {
    // 添加當(dāng)前請求進(jìn)入請求數(shù)組
    [_requestArray addObject:request];
    // 傳入回調(diào)Block時
    if (callback != nil) {
        // 回調(diào)block存入回調(diào)數(shù)組
        [_requestCallbackArray addObject:callback];
    } else {
        // 之前之所以創(chuàng)建一個空的block回調(diào)是為了在用戶不傳入回調(diào)時對應(yīng)傳入創(chuàng)建的空回調(diào),保持添加的請求和回調(diào)個數(shù)一致一一對應(yīng)。
        [_requestCallbackArray addObject:_emptyCallback];
    }
}
start

請求的發(fā)起。

- (void)start {
    // 如果第1個請求已經(jīng)結(jié)束,就不再重復(fù)start了。nextRequestIndex為0,請求重頭開始。
    if (_nextRequestIndex > 0) {
        YTKLog(@"Error! Chain request has already started.");
        return;
    }

    // //如果請求隊列數(shù)組里面還有request,則取出并start
    if ([_requestArray count] > 0) {
        // 將要開始回調(diào)
        [self toggleAccessoriesWillStartCallBack];
        // 開始下一個請求
        [self startNextRequest];
        // 在YTKChainRequestAgent請求數(shù)組中加入當(dāng)前鏈?zhǔn)秸埱螅纯梢杂卸鄠€鏈?zhǔn)秸埱笸瑫r存在)
        [[YTKChainRequestAgent sharedAgent] addChainRequest:self];
    } else {
        YTKLog(@"Error! Chain request array is empty.");
    }
}

- (BOOL)startNextRequest {
    // 判斷不超出數(shù)組
    if (_nextRequestIndex < [_requestArray count]) {
        // 取出request
        YTKBaseRequest *request = _requestArray[_nextRequestIndex];
        // index+1
        _nextRequestIndex++;
        // 指定代理
        request.delegate = self;
        // 清除block
        [request clearCompletionBlock];
        // 開始請求
        [request start];
        return YES;
    } else {
        return NO;
    }
}

所以和批量請求不同的是,鏈?zhǔn)秸埱蟮恼埱箨犃惺强梢宰儎拥?,用戶可以無限制地添加請求。只要請求隊列里面有請求存在,則YTKChainRequest就會繼續(xù)發(fā)送它們。

成功回調(diào)
- (void)requestFinished:(YTKBaseRequest *)request {
    // 獲取當(dāng)前回調(diào)
    NSUInteger currentRequestIndex = _nextRequestIndex - 1;
    YTKChainCallback callback = _requestCallbackArray[currentRequestIndex];
    // 當(dāng)前請求回調(diào)
    callback(self, request);
    //如果沒有下一個請求了
    if (![self startNextRequest]) {
        // 鏈?zhǔn)秸埱髮⒁Y(jié)束
        [self toggleAccessoriesWillStopCallBack];
        // 鏈?zhǔn)秸埱蟠砘卣{(diào)
        if ([_delegate respondsToSelector:@selector(chainRequestFinished:)]) {
            [_delegate chainRequestFinished:self];
            // 從YTKChainRequestAgent列表中移除當(dāng)前鏈?zhǔn)秸埱?            [[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
        }
        // 鏈?zhǔn)秸埱蠼Y(jié)束
        [self toggleAccessoriesDidStopCallBack];
    }
}
失敗回調(diào)
- (void)requestFailed:(YTKBaseRequest *)request {
    //如果當(dāng)前 chain里的某個request失敗了,則判定當(dāng)前chain失敗。調(diào)用當(dāng)前chain失敗的回調(diào)
    [self toggleAccessoriesWillStopCallBack];
    if ([_delegate respondsToSelector:@selector(chainRequestFailed:failedBaseRequest:)]) {
        [_delegate chainRequestFailed:self failedBaseRequest:request];
        [[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
    }
    [self toggleAccessoriesDidStopCallBack];
}

stop
- (void)stop {
    //首先調(diào)用即將停止的callback
    [self toggleAccessoriesWillStopCallBack];
    //然后stop當(dāng)前的請求,再清空chain里所有的請求和回掉block
    [self clearRequest];
    //在YTKChainRequestAgent里移除當(dāng)前的chain
    [[YTKChainRequestAgent sharedAgent] removeChainRequest:self];
    //最后調(diào)用已經(jīng)結(jié)束的callback
    [self toggleAccessoriesDidStopCallBack];
}

- (void)clearRequest {
    //獲取當(dāng)前請求的index
    NSUInteger currentRequestIndex = _nextRequestIndex - 1;
    if (currentRequestIndex < [_requestArray count]) {
        YTKBaseRequest *request = _requestArray[currentRequestIndex];
        // 停止當(dāng)前請求
        [request stop];
    }
    //清空請求數(shù)組
    [_requestArray removeAllObjects];
    //請求回調(diào)數(shù)組
    [_requestCallbackArray removeAllObjects];
}

七.封裝使用

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

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

  • 【1】7,9,-1,5,( ) A、4;B、2;C、-1;D、-3 分析:選D,7+9=16;9+(-1)=8;(...
    Alex_bingo閱讀 19,807評論 1 19
  • 現(xiàn)在APP是無網(wǎng)絡(luò)不APP,現(xiàn)在很少有單機(jī)的APP了,所以任何開發(fā)語言你都得和網(wǎng)絡(luò)接觸.像我這種之前沒有編程經(jīng)驗的...
    小白和小黑閱讀 2,547評論 2 10
  • 代碼下載 服務(wù)端代碼下載地址客戶端代碼下載地址 相關(guān)概念 socket是一個針對TCP和UDP編程的接口,你可以借...
    酒茶白開水閱讀 1,698評論 1 11
  • 小武是個手藝人。他游蕩于人群中,方框眼鏡寬大的舊西裝極具年代色彩,和粗糙的鏡頭顆粒感渾然一體。他時常揮舞起觸手,像...
    四百擊閱讀 695評論 0 1
  • 他發(fā)現(xiàn)自己居然挺期待每天的排練。當(dāng)然是帶著練習(xí)紙下去。本來寫作業(yè)是分內(nèi)之事,現(xiàn)在只要寫兩筆就覺得自己好用功。聽著效...
    何青猊閱讀 283評論 -4 3

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