11.第三方源碼-AFNetworking解析

AFNetWorking
AFNetWorking一款輕量級(jí)網(wǎng)絡(luò)請(qǐng)求開源框架,基于iOS和mac os 網(wǎng)絡(luò)進(jìn)行擴(kuò)展的高性能框架,大大降低了iOS開發(fā)工程師處理網(wǎng)絡(luò)請(qǐng)求的難度,讓iOS開發(fā)變成一件愉快的事情。

GitHub地址:https://github.com/AFNetworking/AFNetworking

AFN優(yōu)點(diǎn):

1.原有基礎(chǔ)urlsesson上封裝了一層,在傳參方面更靈活,
2.回調(diào)更友好,
3.支持返回?cái)?shù)據(jù)序列化
4.支持文件上傳,斷點(diǎn)下載,
5.自帶多線程,防死鎖
6.處理了Https證書流程,節(jié)省移動(dòng)端開發(fā)
7.支持網(wǎng)絡(luò)狀態(tài)判斷

先從最新的AF3.x講起吧:

  • 首先,我們就一起分析一下該框架的組成。
    將AF下載導(dǎo)入工程后,下面是其包結(jié)構(gòu),相對(duì)于2.x變得非常簡單了:
image.png

除去Support Files,可以看到AF分為如下5個(gè)功能模塊:

  • 網(wǎng)絡(luò)通信模塊(AFURLSessionManager、AFHTTPSessionManger)
  • 網(wǎng)絡(luò)狀態(tài)監(jiān)聽模塊(Reachability)
  • 網(wǎng)絡(luò)通信安全策略模塊(Security)
  • 網(wǎng)絡(luò)通信信息序列化/反序列化模塊(Serialization)
  • 對(duì)于iOS UIKit庫的擴(kuò)展(UIKit)


    image.png
其核心當(dāng)然是網(wǎng)絡(luò)通信模塊AFURLSessionManager。大家都知道,AF3.x是基于NSURLSession來封裝的。所以這個(gè)類圍繞著NSURLSession做了一系列的封裝。而其余的四個(gè)模塊,均是為了配合網(wǎng)絡(luò)通信或?qū)σ延蠻IKit的一個(gè)擴(kuò)展工具包。

這五個(gè)模塊所對(duì)應(yīng)的類的結(jié)構(gòu)關(guān)系圖如下所示:

image.png

其中AFHTTPSessionManager是繼承于AFURLSessionManager的,我們一般做網(wǎng)絡(luò)請(qǐng)求都是用這個(gè)類,但是它本身是沒有做實(shí)事的,只是做了一些簡單的封裝,把請(qǐng)求邏輯分發(fā)給父類AFURLSessionManager或者其它類去做。

首先我們簡單的寫個(gè)get請(qǐng)求:
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]init];

[manager GET:@"http://localhost" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

}];

首先我們我們調(diào)用了初始化方法生成了一個(gè)manager,我們點(diǎn)進(jìn)去看看初始化做了什么:

- (instancetype)init {
    return [self initWithBaseURL:nil];
}

- (instancetype)initWithBaseURL:(NSURL *)url {
    return [self initWithBaseURL:url sessionConfiguration:nil];
}

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    return [self initWithBaseURL:nil sessionConfiguration:configuration];
}

- (instancetype)initWithBaseURL:(NSURL *)url
           sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
    self = [super initWithSessionConfiguration:configuration];
    if (!self) {
        return nil;
    }
    //對(duì)傳過來的BaseUrl進(jìn)行處理,如果有值且最后不包含/,url加上"/"
  //--經(jīng)一位熱心讀者更正...以后注釋也一定要走心啊...不能誤導(dǎo)大家...
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }

    self.baseURL = url;

    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];

    return self;
}

  • 初始化都調(diào)用到- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration方法中來了。
  • 其實(shí)初始化方法都調(diào)用父類的初始化方法。父類也就是AF3.x最最核心的類AFURLSessionManager。幾乎所有的類都是圍繞著這個(gè)類在處理業(yè)務(wù)邏輯。
  • 除此之外,方法中把baseURL存了起來,還生成了一個(gè)請(qǐng)求序列對(duì)象和一個(gè)響應(yīng)序列對(duì)象。后面再細(xì)說這兩個(gè)類是干什么用的。

直接來到父類AFURLSessionManager的初始化方法:

- (instancetype)init {
    return [self initWithSessionConfiguration:nil];
}

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    self = [super init];
    if (!self) {
        return nil;
    }
    if (!configuration) {
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }
    self.sessionConfiguration = configuration;
    self.operationQueue = [[NSOperationQueue alloc] init];
    //queue并發(fā)線程數(shù)設(shè)置為1
    self.operationQueue.maxConcurrentOperationCount = 1;

    //注意代理,代理的繼承,實(shí)際上NSURLSession去判斷了,你實(shí)現(xiàn)了哪個(gè)方法會(huì)去調(diào)用,包括子代理的方法!
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

    //各種響應(yīng)轉(zhuǎn)碼
    self.responseSerializer = [AFJSONResponseSerializer serializer];

    //設(shè)置默認(rèn)安全策略
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

#if !TARGET_OS_WATCH
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
    // 設(shè)置存儲(chǔ)NSURL task與AFURLSessionManagerTaskDelegate的詞典(重點(diǎn),在AFNet中,每一個(gè)task都會(huì)被匹配一個(gè)AFURLSessionManagerTaskDelegate 來做task的delegate事件處理) ===============
    self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

    //  設(shè)置AFURLSessionManagerTaskDelegate 詞典的鎖,確保詞典在多線程訪問時(shí)的線程安全
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;

    // 置空task關(guān)聯(lián)的代理
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {        
        for (NSURLSessionDataTask *task in dataTasks) {
            [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
        }
        for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
            [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
        }
        for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
            [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
        }
    }];
    return self;
}

  • 這個(gè)就是最終的初始化方法了,注釋應(yīng)該寫的很清楚,唯一需要說的就是三點(diǎn):
    • self.operationQueue.maxConcurrentOperationCount = 1;這個(gè)operationQueue就是我們代理回調(diào)的queue。這里把代理回調(diào)的線程并發(fā)數(shù)設(shè)置為1了。至于這里為什么要這么做,我們先留一個(gè)坑,等我們講完AF2.x之后再來分析這一塊。
  • 第二就是我們初始化了一些屬性,其中包括self.mutableTaskDelegatesKeyedByTaskIdentifier,這個(gè)是用來讓每一個(gè)請(qǐng)求task和我們自定義的AF代理來建立映射用的,其實(shí)AF對(duì)task的代理進(jìn)行了一個(gè)封裝,并且轉(zhuǎn)發(fā)代理到AF自定義的代理,這是AF比較重要的一部分,接下來我們會(huì)具體講這一塊。
  • 第三就是下面這個(gè)方法:
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { 
}];

首先說說這個(gè)方法是干什么用的:這個(gè)方法用來異步的獲取當(dāng)前session的所有未完成的task。其實(shí)講道理來說在初始化中調(diào)用這個(gè)方法應(yīng)該里面一個(gè)task都不會(huì)有。我們打斷點(diǎn)去看,也確實(shí)如此,里面的數(shù)組都是空的。
但是想想也知道,AF大神不會(huì)把一段沒用的代碼放在這吧。輾轉(zhuǎn)多處,終于從AF的issue中找到了結(jié)論:github 。

  • 原來這是為了防止后臺(tái)回來,重新初始化這個(gè)session,一些之前的后臺(tái)請(qǐng)求任務(wù),導(dǎo)致程序的crash。

初始化方法到這就全部完成了。

image.png

接著我們來看看網(wǎng)絡(luò)請(qǐng)求:

- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                     progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                      success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                      failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
     //生成一個(gè)task
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];

    //開始網(wǎng)絡(luò)請(qǐng)求
    [dataTask resume];

    return dataTask;
}

方法走到類AFHTTPSessionManager中來,調(diào)用父類,也就是我們整個(gè)AF3.x的核心類AFURLSessionManager的方法,生成了一個(gè)系統(tǒng)的NSURLSessionDataTask實(shí)例,并且開始網(wǎng)絡(luò)請(qǐng)求。
我們繼續(xù)往父類里看,看看這個(gè)方法到底做了什么:

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{

    NSError *serializationError = nil;

    //把參數(shù),還有各種東西轉(zhuǎn)化為一個(gè)request
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

    if (serializationError) {
        if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            //如果解析錯(cuò)誤,直接返回
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic pop
        }

        return nil;
    }
    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];

    return dataTask;
}

  • 這個(gè)方法做了兩件事:
    1.用self.requestSerializer和各種參數(shù)去獲取了一個(gè)我們最終請(qǐng)求網(wǎng)絡(luò)需要的NSMutableURLRequest實(shí)例。
    2.調(diào)用另外一個(gè)方法dataTaskWithRequest去拿到我們最終需要的NSURLSessionDataTask實(shí)例,并且在完成的回調(diào)里,調(diào)用我們傳過來的成功和失敗的回調(diào)。
  • 注意下面這個(gè)方法,我們常用來 push pop搭配,來忽略一些編譯器的警告:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop

這里是用來忽略:?帶來的警告,具體的各種編譯器警告描述,可以參考這篇:各種編譯器的警告

  • 說到底這個(gè)方法還是沒有做實(shí)事,我們繼續(xù)到requestSerializer方法里去看,看看AF到底如何拼接成我們需要的request的:

接著我們跑到AFURLRequestSerialization類中:

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    //斷言,debug模式下,如果缺少改參數(shù),crash
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];

    NSParameterAssert(url);

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;

    //將request的各種屬性循環(huán)遍歷
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        //如果自己觀察到的發(fā)生變化的屬性,在這些方法里
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
           //把給自己設(shè)置的屬性給request設(shè)置
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }
    //將傳入的parameters進(jìn)行編碼,并添加到request中
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

    return mutableRequest;
}

  • 講一下這個(gè)方法,這個(gè)方法做了3件事:
    1)設(shè)置request的請(qǐng)求類型,get,post,put...等
    2)往request里添加一些參數(shù)設(shè)置,其中AFHTTPRequestSerializerObservedKeyPaths()是一個(gè)c函數(shù),返回一個(gè)數(shù)組,我們來看看這個(gè)函數(shù):
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
    static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
    static dispatch_once_t onceToken;
    // 此處需要observer的keypath為allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies
    // HTTPShouldUsePipelining、networkServiceType、timeoutInterval
    dispatch_once(&onceToken, ^{
        _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
    });
    //就是一個(gè)數(shù)組里裝了很多方法的名字,
    return _AFHTTPRequestSerializerObservedKeyPaths;
}

其實(shí)這個(gè)函數(shù)就是封裝了一些屬性的名字,這些都是NSUrlRequest的屬性。
再來看看self.mutableObservedChangedKeyPaths,這個(gè)是當(dāng)前類的一個(gè)屬性:

@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;

在-init方法對(duì)這個(gè)集合進(jìn)行了初始化,并且對(duì)當(dāng)前類的和NSUrlRequest相關(guān)的那些屬性添加了KVO監(jiān)聽

 //每次都會(huì)重置變化
    self.mutableObservedChangedKeyPaths = [NSMutableSet set];

    //給這自己些方法添加觀察者為自己,就是request的各種屬性,set方法
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
        }
    }

KVO觸發(fā)的方法:

-(void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(__unused id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    //當(dāng)觀察到這些set方法被調(diào)用了,而且不為Null就會(huì)添加到集合里,否則移除
    if (context == AFHTTPRequestSerializerObserverContext) {
        if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
            [self.mutableObservedChangedKeyPaths removeObject:keyPath];
        } else {
            [self.mutableObservedChangedKeyPaths addObject:keyPath];
        }
    }
}

至此我們知道self.mutableObservedChangedKeyPaths其實(shí)就是我們自己設(shè)置的request屬性值的集合。
接下來調(diào)用:

[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];

用KVC的方式,把屬性值都設(shè)置到我們請(qǐng)求的request中去。

3)把需要傳遞的參數(shù)進(jìn)行編碼,并且設(shè)置到request中去:

//將傳入的parameters進(jìn)行編碼,并添加到request中
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

 - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);

    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    //從自己的head里去遍歷,如果有值則設(shè)置給request的head
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    //來把各種類型的參數(shù),array dic set轉(zhuǎn)化成字符串,給request
    NSString *query = nil;
    if (parameters) {
        //自定義的解析方式
        if (self.queryStringSerialization) {
            NSError *serializationError;
            query = self.queryStringSerialization(request, parameters, &serializationError);

            if (serializationError) {
                if (error) {
                    *error = serializationError;
                }

                return nil;
            }
        } else {
            //默認(rèn)解析方式
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    //最后判斷該request中是否包含了GET、HEAD、DELETE(都包含在HTTPMethodsEncodingParametersInURI)。因?yàn)檫@幾個(gè)method的quey是拼接到url后面的。而POST、PUT是把query拼接到http body中的。
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    } else {
        //post put請(qǐng)求

        // #2864: an empty string is a valid x-www-form-urlencoded payload
        if (!query) {
            query = @"";
        }
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        //設(shè)置請(qǐng)求體
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }

    return mutableRequest;
}

這個(gè)方法做了3件事:
1.從self.HTTPRequestHeaders中拿到設(shè)置的參數(shù),賦值要請(qǐng)求的request里去
2.把請(qǐng)求網(wǎng)絡(luò)的參數(shù),從array dic set這些容器類型轉(zhuǎn)換為字符串,具體轉(zhuǎn)碼方式,我們可以使用自定義的方式,也可以用AF默認(rèn)的轉(zhuǎn)碼方式。自定義的方式?jīng)]什么好說的,想怎么去解析由你自己來決定。我們可以來看看默認(rèn)的方式:

NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];

    //把參數(shù)給AFQueryStringPairsFromDictionary,拿到AF的一個(gè)類型的數(shù)據(jù)就一個(gè)key,value對(duì)象,在URLEncodedStringValue拼接keyValue,一個(gè)加到數(shù)組里
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }

    //拆分?jǐn)?shù)組返回參數(shù)字符串
    return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    //往下調(diào)用
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

    // 根據(jù)需要排列的對(duì)象的description來進(jìn)行升序排列,并且selector使用的是compare:
    // 因?yàn)閷?duì)象的description返回的是NSString,所以此處compare:使用的是NSString的compare函數(shù)
    // 即@[@"foo", @"bar", @"bae"] ----> @[@"bae", @"bar",@"foo"]
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    //判斷vaLue是什么類型的,然后去遞歸調(diào)用自己,直到解析的是除了array dic set以外的元素,然后把得到的參數(shù)數(shù)組返回。
    if ([value isKindOfClass:[NSDictionary class]]) {
        NSDictionary *dictionary = value;
        // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries

        //拿到
        for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            id nestedValue = dictionary[nestedKey];
            if (nestedValue) {
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
            }
        }
    } else if ([value isKindOfClass:[NSArray class]]) {
        NSArray *array = value;
        for (id nestedValue in array) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
        }
    } else if ([value isKindOfClass:[NSSet class]]) {
        NSSet *set = value;
        for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
        }
    } else {
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    return mutableQueryStringComponents;
}

  • 轉(zhuǎn)碼主要是以上三個(gè)函數(shù),配合著注釋應(yīng)該也很好理解:主要是在遞歸調(diào)用AFQueryStringPairsFromKeyAndValue。判斷vaLue是什么類型的,然后去遞歸調(diào)用自己,直到解析的是除了array dic set以外的元素,然后把得到的參數(shù)數(shù)組返回。
  • 其中有個(gè)AFQueryStringPair對(duì)象,其只有兩個(gè)屬性和兩個(gè)方法:
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;

    - (instancetype)initWithField:(id)field value:(id)value {
    self = [super init];
    if (!self) {
        return nil;
    }

    self.field = field;
    self.value = value;

    return self;
}

   - (NSString *)URLEncodedStringValue {
    if (!self.value || [self.value isEqual:[NSNull null]]) {
        return AFPercentEscapedStringFromString([self.field description]);
    } else {
        return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
    }
}

方法很簡單,現(xiàn)在我們也很容易理解這整個(gè)轉(zhuǎn)碼過程了,我們舉個(gè)例子梳理下,就是以下這3步:

@{ 
     @"name" : @"bang", 
     @"phone": @{@"mobile": @"xx", @"home": @"xx"}, 
     @"families": @[@"father", @"mother"], 
     @"nums": [NSSet setWithObjects:@"1", @"2", nil] 
} 
-> 
@[ 
     field: @"name", value: @"bang", 
     field: @"phone[mobile]", value: @"xx", 
     field: @"phone[home]", value: @"xx", 
     field: @"families[]", value: @"father", 
     field: @"families[]", value: @"mother", 
     field: @"nums", value: @"1", 
     field: @"nums", value: @"2", 
] 
-> 
name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2

至此,我們原來的容器類型的參數(shù),就這樣變成字符串類型了。

緊接著這個(gè)方法還根據(jù)該request中請(qǐng)求類型,來判斷參數(shù)字符串應(yīng)該如何設(shè)置到request中去。如果是GET、HEAD、DELETE,則把參數(shù)quey是拼接到url后面的。而POST、PUT是把query拼接到http body中的:

if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
    if (query && query.length > 0) {
        mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
    }
} else {
    //post put請(qǐng)求

    // #2864: an empty string is a valid x-www-form-urlencoded payload
    if (!query) {
        query = @"";
    }
    if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
        [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    }
    //設(shè)置請(qǐng)求體
    [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}

至此,我們生成了一個(gè)request。

image.png
我們再回到AFHTTPSessionManager類中來,回到這個(gè)方法:
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
    NSError *serializationError = nil;
    //把參數(shù),還有各種東西轉(zhuǎn)化為一個(gè)request
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

    if (serializationError) {
        if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            //如果解析錯(cuò)誤,直接返回
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic pop
        }

        return nil;
    }

    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];
    return dataTask;
}

繞了一圈我們又回來了。。

  • 我們繼續(xù)往下看:當(dāng)解析錯(cuò)誤,我們直接調(diào)用傳進(jìn)來的fauler的Block失敗返回了,這里有一個(gè)self.completionQueue,這個(gè)是我們自定義的,這個(gè)是一個(gè)GCD的Queue如果設(shè)置了那么從這個(gè)Queue中回調(diào)結(jié)果,否則從主隊(duì)列回調(diào)。

  • 實(shí)際上這個(gè)Queue還是挺有用的,之前還用到過。我們公司有自己的一套數(shù)據(jù)加解密的解析模式,所以我們回調(diào)回來的數(shù)據(jù)并不想是主線程,我們可以設(shè)置這個(gè)Queue,在分線程進(jìn)行解析數(shù)據(jù),然后自己再調(diào)回到主線程去刷新UI。

言歸正傳,我們接著調(diào)用了父類的生成task的方法,并且執(zhí)行了一個(gè)成功和失敗的回調(diào),我們接著去父類AFURLSessionManger里看(總算到我們的核心類了..):

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {

    __block NSURLSessionDataTask *dataTask = nil;
    //第一件事,創(chuàng)建NSURLSessionDataTask,里面適配了Ios8以下taskIdentifiers,函數(shù)創(chuàng)建task對(duì)象。
    //其實(shí)現(xiàn)應(yīng)該是因?yàn)閕OS 8.0以下版本中會(huì)并發(fā)地創(chuàng)建多個(gè)task對(duì)象,而同步有沒有做好,導(dǎo)致taskIdentifiers 不唯一…這邊做了一個(gè)串行處理,串行隊(duì)列里面還有用GCD創(chuàng)建了個(gè)單例
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

  • 我們注意到這個(gè)方法非常簡單,就調(diào)用了一個(gè)url_session_manager_create_task_safely()函數(shù),傳了一個(gè)Block進(jìn)去,Block里就是iOS原生生成dataTask的方法。此外,還調(diào)用了一個(gè)addDelegateForDataTask的方法。
  • 我們到這先到這個(gè)函數(shù)里去看看:
static void url_session_manager_create_task_safely(dispatch_block_t block) {
    if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
        // Fix of bug
        // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
        // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093

      //理解下,第一為什么用sync,因?yàn)槭窍胍骶€程等在這,等執(zhí)行完,在返回,因?yàn)楸仨殘?zhí)行完dataTask才有數(shù)據(jù),傳值才有意義。
      //第二,為什么要用串行隊(duì)列,因?yàn)檫@塊是為了防止ios8以下內(nèi)部的dataTaskWithRequest是并發(fā)創(chuàng)建的,
      //這樣會(huì)導(dǎo)致taskIdentifiers這個(gè)屬性值不唯一,因?yàn)楹罄m(xù)要用taskIdentifiers來作為Key對(duì)應(yīng)delegate。
        dispatch_sync(url_session_manager_creation_queue(), block);
    } else {
        block();
    }
}
static dispatch_queue_t url_session_manager_creation_queue() {
    static dispatch_queue_t af_url_session_manager_creation_queue;
    static dispatch_once_t onceToken;
    //保證了即使是在多線程的環(huán)境下,也不會(huì)創(chuàng)建其他隊(duì)列
    dispatch_once(&onceToken, ^{
        af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
    });

    return af_url_session_manager_creation_queue;
}

  • 方法非常簡單,關(guān)鍵是理解這么做的目的:為什么我們不直接去調(diào)用
    dataTask = [self.session dataTaskWithRequest:request];
    非要繞這么一圈,我們點(diǎn)進(jìn)去bug日志里看看,原來這是為了適配iOS8的以下,創(chuàng)建session的時(shí)候,偶發(fā)的情況會(huì)出現(xiàn)session的屬性taskIdentifier這個(gè)值不唯一,而這個(gè)taskIdentifier是我們后面來映射delegate的key,所以它必須是唯一的。
  • 具體原因應(yīng)該是NSURLSession內(nèi)部去生成task的時(shí)候是用多線程并發(fā)去執(zhí)行的。想通了這一點(diǎn),我們就很好解決了,我們只需要在iOS8以下同步串行的去生成task就可以防止這一問題發(fā)生(如果還是不理解同步串行的原因,可以看看注釋)。
  • 題外話:很多同學(xué)都會(huì)抱怨為什么sync我從來用不到,看,有用到的地方了吧,很多東西不是沒用,而只是你想不到怎么用。

我們接著看到:

[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

調(diào)用到:

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
              downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];

    // AFURLSessionManagerTaskDelegate與AFURLSessionManager建立相互關(guān)系
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    //這個(gè)taskDescriptionForSessionTasks用來發(fā)送開始和掛起通知的時(shí)候會(huì)用到,就是用這個(gè)值來Post通知,來兩者對(duì)應(yīng)
    dataTask.taskDescription = self.taskDescriptionForSessionTasks;

    // ***** 將AF delegate對(duì)象與 dataTask建立關(guān)系
    [self setDelegate:delegate forTask:dataTask];

    // 設(shè)置AF delegate的上傳進(jìn)度,下載進(jìn)度塊。
    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

  • 總結(jié)一下:
    1)這個(gè)方法,生成了一個(gè)AFURLSessionManagerTaskDelegate,這個(gè)其實(shí)就是AF的自定義代理。我們請(qǐng)求傳來的參數(shù),都賦值給這個(gè)AF的代理了。
    2)delegate.manager = self;代理把AFURLSessionManager這個(gè)類作為屬性了,我們可以看到:
@property (nonatomic, weak) AFURLSessionManager *manager;

這個(gè)屬性是弱引用的,所以不會(huì)存在循環(huán)引用的問題。
3)我們調(diào)用了[self setDelegate:delegate forTask:dataTask];

我們進(jìn)去看看這個(gè)方法做了什么:

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    //斷言,如果沒有這個(gè)參數(shù),debug下crash在這
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    //加鎖保證字典線程安全
    [self.lock lock];
    // 將AF delegate放入以taskIdentifier標(biāo)記的詞典中(同一個(gè)NSURLSession中的taskIdentifier是唯一的)
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;

    // 為AF delegate 設(shè)置task 的progress監(jiān)聽
    [delegate setupProgressForTask:task];

    //添加task開始和暫停的通知
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

  • 這個(gè)方法主要就是把AF代理和task建立映射,存在了一個(gè)我們事先聲明好的字典里。
  • 而要加鎖的原因是因?yàn)楸旧砦覀冞@個(gè)字典屬性是mutable的,是線程不安全的。而我們對(duì)這些方法的調(diào)用,確實(shí)是會(huì)在復(fù)雜的多線程環(huán)境中,后面會(huì)仔細(xì)提到線程問題。
  • 還有個(gè)[delegate setupProgressForTask:task];我們到方法里去看看:
- (void)setupProgressForTask:(NSURLSessionTask *)task {

    __weak __typeof__(task) weakTask = task;

    //拿到上傳下載期望的數(shù)據(jù)大小
    self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
    self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;

    //將上傳與下載進(jìn)度和 任務(wù)綁定在一起,直接cancel suspend resume進(jìn)度條,可以cancel...任務(wù)
    [self.uploadProgress setCancellable:YES];
    [self.uploadProgress setCancellationHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask cancel];
    }];
    [self.uploadProgress setPausable:YES];
    [self.uploadProgress setPausingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask suspend];
    }];

    if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
        [self.uploadProgress setResumingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask resume];
        }];
    }

    [self.downloadProgress setCancellable:YES];
    [self.downloadProgress setCancellationHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask cancel];
    }];
    [self.downloadProgress setPausable:YES];
    [self.downloadProgress setPausingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask suspend];
    }];

    if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
        [self.downloadProgress setResumingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask resume];
        }];
    }

    //觀察task的這些屬性
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
              options:NSKeyValueObservingOptionNew
              context:NULL];
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
              options:NSKeyValueObservingOptionNew
              context:NULL];

    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))
              options:NSKeyValueObservingOptionNew
              context:NULL];
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))
              options:NSKeyValueObservingOptionNew
              context:NULL];

    //觀察progress這兩個(gè)屬性
    [self.downloadProgress addObserver:self
                            forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                               options:NSKeyValueObservingOptionNew
                               context:NULL];
    [self.uploadProgress addObserver:self
                          forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
}

  • 這個(gè)方法也非常簡單,主要做了以下幾件事:
    1)設(shè)置 downloadProgressuploadProgress的一些屬性,并且把兩者和task的任務(wù)狀態(tài)綁定在了一起。注意這兩者都是NSProgress的實(shí)例對(duì)象,(這里可能又一群小伙伴楞在這了,這是個(gè)什么...)簡單來說,這就是iOS7引進(jìn)的一個(gè)用來管理進(jìn)度的類,可以開始,暫停,取消,完整的對(duì)應(yīng)了task的各種狀態(tài),當(dāng)progress進(jìn)行各種操作的時(shí)候,task也會(huì)引發(fā)對(duì)應(yīng)操作。
    2)給task和progress的各個(gè)屬及添加KVO監(jiān)聽,至于監(jiān)聽了干什么用,我們接著往下看:
 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {

    //是task
    if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
        //給進(jìn)度條賦新值
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
            self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
            self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
            self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
            self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        }
    }
    //上面的賦新值會(huì)觸發(fā)這兩個(gè),調(diào)用block回調(diào),用戶拿到進(jìn)度
    else if ([object isEqual:self.downloadProgress]) {
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
    else if ([object isEqual:self.uploadProgress]) {
        if (self.uploadProgressBlock) {
            self.uploadProgressBlock(object);
        }
    }
}

  • 方法非常簡單直觀,主要就是如果task觸發(fā)KVO,則給progress進(jìn)度賦值,應(yīng)為賦值了,所以會(huì)觸發(fā)progress的KVO,也會(huì)調(diào)用到這里,然后去執(zhí)行我們傳進(jìn)來的downloadProgressBlockuploadProgressBlock。主要的作用就是為了讓進(jìn)度實(shí)時(shí)的傳遞。

  • 主要是觀摩一下大神的寫代碼的結(jié)構(gòu),這個(gè)解耦的編程思想,不愧是大神...

  • 還有一點(diǎn)需要注意:我們之前的setProgress和這個(gè)KVO監(jiān)聽,都是在我們AF自定義的delegate內(nèi)的,是有一個(gè)task就會(huì)有一個(gè)delegate的。所以說我們是每個(gè)task都會(huì)去監(jiān)聽這些屬性,分別在各自的AF代理內(nèi)。看到這,可能有些小伙伴會(huì)有點(diǎn)亂,沒關(guān)系。等整個(gè)講完之后我們還會(huì)詳細(xì)的去講捋一捋manager、task、還有AF自定義代理三者之前的對(duì)應(yīng)關(guān)系。

到這里我們整個(gè)對(duì)task的處理就完成了。

image.png

接著task就開始請(qǐng)求網(wǎng)絡(luò)了,還記得我們初始化方法中:

self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

我們把AFUrlSessionManager作為了所有的task的delegate。當(dāng)我們請(qǐng)求網(wǎng)絡(luò)的時(shí)候,這些代理開始調(diào)用了:

image.png
  • AFUrlSessionManager一共實(shí)現(xiàn)了如上圖所示這么一大堆NSUrlSession相關(guān)的代理。(小伙伴們的順序可能不一樣,樓主根據(jù)代理隸屬重新排序了一下)

  • 而只轉(zhuǎn)發(fā)了其中3條到AF自定義的delegate中:

image.png

這就是我們一開始說的,AFUrlSessionManager對(duì)這一大堆代理做了一些公共的處理,而轉(zhuǎn)發(fā)到AF自定義代理的3條,則負(fù)責(zé)把每個(gè)task對(duì)應(yīng)的數(shù)據(jù)回調(diào)出去。

又有小伙伴問了,我們設(shè)置的這個(gè)代理不是NSURLSessionDelegate嗎?怎么能響應(yīng)NSUrlSession這么多代理呢?我們點(diǎn)到類的聲明文件中去看看:

@protocol NSURLSessionDelegate <NSObject>
@protocol NSURLSessionTaskDelegate <NSURLSessionDelegate>
@protocol NSURLSessionDataDelegate <NSURLSessionTaskDelegate>
@protocol NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
@protocol NSURLSessionStreamDelegate <NSURLSessionTaskDelegate>

  • 我們可以看到這些代理都是繼承關(guān)系,而在NSURLSession實(shí)現(xiàn)中,只要設(shè)置了這個(gè)代理,它會(huì)去判斷這些所有的代理,是否respondsToSelector這些代理中的方法,如果響應(yīng)了就會(huì)去調(diào)用。
  • 而AF還重寫了respondsToSelector方法:
 - (BOOL)respondsToSelector:(SEL)selector {

    //復(fù)寫了selector的方法,這幾個(gè)方法是在本類有實(shí)現(xiàn)的,但是如果外面的Block沒賦值的話,則返回NO,相當(dāng)于沒有實(shí)現(xiàn)!
    if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) {
        return self.taskWillPerformHTTPRedirection != nil;
    } else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) {
        return self.dataTaskDidReceiveResponse != nil;
    } else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) {
        return self.dataTaskWillCacheResponse != nil;
    } else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) {
        return self.didFinishEventsForBackgroundURLSession != nil;
    }
    return [[self class] instancesRespondToSelector:selector];
}

這樣如果沒實(shí)現(xiàn)這些我們自定義的Block也不會(huì)去回調(diào)這些代理。因?yàn)楸旧砟承┐恚粓?zhí)行了這些自定義的Block,如果Block都沒有賦值,那我們調(diào)用代理也沒有任何意義。
講到這,我們順便看看AFUrlSessionManager的一些自定義Block:

@property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
@property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession;
@property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidReceiveAuthenticationChallengeBlock taskDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyData;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidComplete;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadTask;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveData;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskWillCacheResponseBlock dataTaskWillCacheResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidWriteDataBlock downloadTaskDidWriteData;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidResume;

各自對(duì)應(yīng)的還有一堆這樣的set方法:

 - (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block {
    self.sessionDidBecomeInvalid = block;
}

方法都是一樣的,就不重復(fù)粘貼占篇幅了。
主要談?wù)勥@個(gè)設(shè)計(jì)思路

  • 作者用@property把這個(gè)些Block屬性在.m文件中聲明,然后復(fù)寫了set方法。
  • 然后在.h中去聲明這些set方法:
   - (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;

為什么要繞這么一大圈呢?原來這是為了我們這些用戶使用起來方便,調(diào)用set方法去設(shè)置這些Block,能很清晰的看到Block的各個(gè)參數(shù)與返回值。大神的精髓的編程思想無處不體現(xiàn)...

接下來我們就講講這些代理方法做了什么(按照順序來):

NSURLSessionDelegate
代理1:
//當(dāng)前這個(gè)session已經(jīng)失效時(shí),該代理方法被調(diào)用。
/*
 如果你使用finishTasksAndInvalidate函數(shù)使該session失效,
 那么session首先會(huì)先完成最后一個(gè)task,然后再調(diào)用URLSession:didBecomeInvalidWithError:代理方法,
 如果你調(diào)用invalidateAndCancel方法來使session失效,那么該session會(huì)立即調(diào)用上面的代理方法。
 */
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
    if (self.sessionDidBecomeInvalid) {
        self.sessionDidBecomeInvalid(session, error);
    }

    [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}

  • 方法調(diào)用時(shí)機(jī)注釋寫的很清楚,就調(diào)用了一下我們自定義的Block,還發(fā)了一個(gè)失效的通知,至于這個(gè)通知有什么用。很抱歉,AF沒用它做任何事,只是發(fā)了...目的是用戶自己可以利用這個(gè)通知做什么事吧。
  • 其實(shí)AF大部分通知都是如此。當(dāng)然,還有一部分通知AF還是有自己用到的,包括配合對(duì)UIKit的一些擴(kuò)展來使用,后面我們會(huì)有單獨(dú)篇幅展開講講這些UIKit的擴(kuò)展類的實(shí)現(xiàn)。
代理2:
//2、https認(rèn)證
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    //挑戰(zhàn)處理類型為 默認(rèn)
    /*
     NSURLSessionAuthChallengePerformDefaultHandling:默認(rèn)方式處理
     NSURLSessionAuthChallengeUseCredential:使用指定的證書
     NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰(zhàn)
     */
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    // sessionDidReceiveAuthenticationChallenge是自定義方法,用來如何應(yīng)對(duì)服務(wù)器端的認(rèn)證挑戰(zhàn)

    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        // 此處服務(wù)器要求客戶端的接收認(rèn)證挑戰(zhàn)方法是NSURLAuthenticationMethodServerTrust
        // 也就是說服務(wù)器端需要客戶端返回一個(gè)根據(jù)認(rèn)證挑戰(zhàn)的保護(hù)空間提供的信任(即challenge.protectionSpace.serverTrust)產(chǎn)生的挑戰(zhàn)證書。

        // 而這個(gè)證書就需要使用credentialForTrust:來創(chuàng)建一個(gè)NSURLCredential對(duì)象
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {

            // 基于客戶端的安全策略來決定是否信任該服務(wù)器,不信任的話,也就沒必要響應(yīng)挑戰(zhàn)
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {

                // 創(chuàng)建挑戰(zhàn)證書(注:挑戰(zhàn)方式為UseCredential和PerformDefaultHandling都需要新建挑戰(zhàn)證書)
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                // 確定挑戰(zhàn)的方式
                if (credential) {
                    //證書挑戰(zhàn)
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    //默認(rèn)挑戰(zhàn)  唯一區(qū)別,下面少了這一步!
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                //取消挑戰(zhàn)
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            //默認(rèn)挑戰(zhàn)方式
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }
    //完成挑戰(zhàn)
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

  • 函數(shù)作用:
    web服務(wù)器接收到客戶端請(qǐng)求時(shí),有時(shí)候需要先驗(yàn)證客戶端是否為正常用戶,再?zèng)Q定是夠返回真實(shí)數(shù)據(jù)。這種情況稱之為服務(wù)端要求客戶端接收挑戰(zhàn)(NSURLAuthenticationChallenge *challenge)。接收到挑戰(zhàn)后,客戶端要根據(jù)服務(wù)端傳來的challenge來生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential(disposition指定應(yīng)對(duì)這個(gè)挑戰(zhàn)的方法,而credential是客戶端生成的挑戰(zhàn)證書,注意只有challenge中認(rèn)證方法為NSURLAuthenticationMethodServerTrust的時(shí)候,才需要生成挑戰(zhàn)證書)。最后調(diào)用completionHandler回應(yīng)服務(wù)器端的挑戰(zhàn)。
  • 函數(shù)討論:
    該代理方法會(huì)在下面兩種情況調(diào)用:
  1. 當(dāng)服務(wù)器端要求客戶端提供證書時(shí)或者進(jìn)行NTLM認(rèn)證(Windows NT LAN Manager,微軟提出的WindowsNT挑戰(zhàn)/響應(yīng)驗(yàn)證機(jī)制)時(shí),此方法允許你的app提供正確的挑戰(zhàn)證書。
  2. 當(dāng)某個(gè)session使用SSL/TLS協(xié)議,第一次和服務(wù)器端建立連接的時(shí)候,服務(wù)器會(huì)發(fā)送給iOS客戶端一個(gè)證書,此方法允許你的app驗(yàn)證服務(wù)期端的證書鏈(certificate keychain)
    注:如果你沒有實(shí)現(xiàn)該方法,該session會(huì)調(diào)用其NSURLSessionTaskDelegate的代理方法URLSession:task:didReceiveChallenge:completionHandler: 。

這里,我把官方文檔對(duì)這個(gè)方法的描述翻譯了一下。
總結(jié)一下,這個(gè)方法其實(shí)就是做https認(rèn)證的??纯瓷厦娴淖⑨?,大概能看明白這個(gè)方法做認(rèn)證的步驟,我們還是如果有自定義的做認(rèn)證的Block,則調(diào)用我們自定義的,否則去執(zhí)行默認(rèn)的認(rèn)證步驟,最后調(diào)用完成認(rèn)證:

//完成挑戰(zhàn) 
if (completionHandler) { 
      completionHandler(disposition, credential); 
}

代理3:
//3、 當(dāng)session中所有已經(jīng)入隊(duì)的消息被發(fā)送出去后,會(huì)調(diào)用該代理方法。
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    if (self.didFinishEventsForBackgroundURLSession) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.didFinishEventsForBackgroundURLSession(session);
        });
    }
}

官方文檔翻譯:

函數(shù)討論:

  • 在iOS中,當(dāng)一個(gè)后臺(tái)傳輸任務(wù)完成或者后臺(tái)傳輸時(shí)需要證書,而此時(shí)你的app正在后臺(tái)掛起,那么你的app在后臺(tái)會(huì)自動(dòng)重新啟動(dòng)運(yùn)行,并且這個(gè)app的UIApplicationDelegate會(huì)發(fā)送一個(gè)application:handleEventsForBackgroundURLSession:completionHandler:消息。該消息包含了對(duì)應(yīng)后臺(tái)的session的identifier,而且這個(gè)消息會(huì)導(dǎo)致你的app啟動(dòng)。你的app隨后應(yīng)該先存儲(chǔ)completion handler,然后再使用相同的identifier創(chuàng)建一個(gè)background configuration,并根據(jù)這個(gè)background configuration創(chuàng)建一個(gè)新的session。這個(gè)新創(chuàng)建的session會(huì)自動(dòng)與后臺(tái)任務(wù)重新關(guān)聯(lián)在一起。
  • 當(dāng)你的app獲取了一個(gè)URLSessionDidFinishEventsForBackgroundURLSession:消息,這就意味著之前這個(gè)session中已經(jīng)入隊(duì)的所有消息都轉(zhuǎn)發(fā)出去了,這時(shí)候再調(diào)用先前存取的completion handler是安全的,或者因?yàn)閮?nèi)部更新而導(dǎo)致調(diào)用completion handler也是安全的。
NSURLSessionTaskDelegate
代理4:
//被服務(wù)器重定向的時(shí)候調(diào)用
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
        newRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLRequest *))completionHandler
{
    NSURLRequest *redirectRequest = request;

    // step1\. 看是否有對(duì)應(yīng)的user block 有的話轉(zhuǎn)發(fā)出去,通過這4個(gè)參數(shù),返回一個(gè)NSURLRequest類型參數(shù),request轉(zhuǎn)發(fā)、網(wǎng)絡(luò)重定向.
    if (self.taskWillPerformHTTPRedirection) {
        //用自己自定義的一個(gè)重定向的block實(shí)現(xiàn),返回一個(gè)新的request。
        redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
    }

    if (completionHandler) {
        // step2\. 用request重新請(qǐng)求
        completionHandler(redirectRequest);
    }
}

  • 一開始我以為這個(gè)方法是類似NSURLProtocol,可以在請(qǐng)求時(shí)自己主動(dòng)的去重定向request,后來發(fā)現(xiàn)不是,這個(gè)方法是在服務(wù)器去重定向的時(shí)候,才會(huì)被調(diào)用。為此我寫了段簡單的PHP測了測:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Welcome extends CI_Controller {
    public function index()
    {
        header("location: http://www.huixionghome.cn/");
    }
}

證實(shí)確實(shí)如此,當(dāng)我們服務(wù)器重定向的時(shí)候,代理就被調(diào)用了,我們可以去重新定義這個(gè)重定向的request。

  • 關(guān)于這個(gè)代理還有一些需要注意的地方:

此方法只會(huì)在default session或者ephemeral session中調(diào)用,而在background session中,session task會(huì)自動(dòng)重定向。

這里指的模式是我們一開始Init的模式:

if (!configuration) {
    configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;

這個(gè)模式總共分為3種:

對(duì)于NSURLSession對(duì)象的初始化需要使用NSURLSessionConfiguration,而NSURLSessionConfiguration有三個(gè)類工廠方法:
+defaultSessionConfiguration 返回一個(gè)標(biāo)準(zhǔn)的 configuration,這個(gè)配置實(shí)際上與 NSURLConnection 的網(wǎng)絡(luò)堆棧(networking stack)是一樣的,具有相同的共享 NSHTTPCookieStorage,共享 NSURLCache 和共享NSURLCredentialStorage。
+ephemeralSessionConfiguration 返回一個(gè)預(yù)設(shè)配置,這個(gè)配置中不會(huì)對(duì)緩存,Cookie 和證書進(jìn)行持久性的存儲(chǔ)。這對(duì)于實(shí)現(xiàn)像秘密瀏覽這種功能來說是很理想的。
+backgroundSessionConfiguration:(NSString *)identifier 的獨(dú)特之處在于,它會(huì)創(chuàng)建一個(gè)后臺(tái) session。后臺(tái) session 不同于常規(guī)的,普通的 session,它甚至可以在應(yīng)用程序掛起,退出或者崩潰的情況下運(yùn)行上傳和下載任務(wù)。初始化時(shí)指定的標(biāo)識(shí)符,被用于向任何可能在進(jìn)程外恢復(fù)后臺(tái)傳輸?shù)氖刈o(hù)進(jìn)程(daemon)提供上下文。

代理5:
//https認(rèn)證
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.taskDidReceiveAuthenticationChallenge) {
        disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
    } else {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                disposition = NSURLSessionAuthChallengeUseCredential;
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

  • 鑒于篇幅,就不去貼官方文檔的翻譯了,大概總結(jié)一下:
    之前我們也有一個(gè)https認(rèn)證,功能一樣,執(zhí)行的內(nèi)容也完全一樣。
  • 區(qū)別在于這個(gè)是non-session-level級(jí)別的認(rèn)證,而之前的是session-level級(jí)別的。
  • 相對(duì)于它,多了一個(gè)參數(shù)task,然后調(diào)用我們自定義的Block會(huì)多回傳這個(gè)task作為參數(shù),這樣我們就可以根據(jù)每個(gè)task去自定義我們需要的https認(rèn)證方式。
代理6:
//當(dāng)一個(gè)session task需要發(fā)送一個(gè)新的request body stream到服務(wù)器端的時(shí)候,調(diào)用該代理方法。

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
 needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
{

    NSInputStream *inputStream = nil;

    //有自定義的taskNeedNewBodyStream,用自定義的,不然用task里原始的stream
    if (self.taskNeedNewBodyStream) {
        inputStream = self.taskNeedNewBodyStream(session, task);
    } else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
        inputStream = [task.originalRequest.HTTPBodyStream copy];
    }

    if (completionHandler) {
        completionHandler(inputStream);
    }
}

  • 該代理方法會(huì)在下面兩種情況被調(diào)用:
    1. 如果task是由uploadTaskWithStreamedRequest:創(chuàng)建的,那么提供初始的request body stream時(shí)候會(huì)調(diào)用該代理方法。
    2. 因?yàn)檎J(rèn)證挑戰(zhàn)或者其他可恢復(fù)的服務(wù)器錯(cuò)誤,而導(dǎo)致需要客戶端重新發(fā)送一個(gè)含有body stream的request,這時(shí)候會(huì)調(diào)用該代理。
代理7:
/*
 //周期性地通知代理發(fā)送到服務(wù)器端數(shù)據(jù)的進(jìn)度。
 */

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
     // 如果totalUnitCount獲取失敗,就使用HTTP header中的Content-Length作為totalUnitCount

    int64_t totalUnitCount = totalBytesExpectedToSend;
    if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
        NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
        if(contentLength) {
            totalUnitCount = (int64_t) [contentLength longLongValue];
        }
    }

    if (self.taskDidSendBodyData) {
        self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
    }
}

  • 就是每次發(fā)送數(shù)據(jù)給服務(wù)器,會(huì)回調(diào)這個(gè)方法,通知已經(jīng)發(fā)送了多少,總共要發(fā)送多少。
  • 代理方法里也就是僅僅調(diào)用了我們自定義的Block而已。
未完總結(jié):
  • 其實(shí)寫了這么多,還沒有講到真正重要的地方,但是因?yàn)橐呀?jīng)接近簡書最大篇幅,所以只能先在這里結(jié)個(gè)尾了。
  • 如果能看到這里,說明你是個(gè)非常有耐心,非常好學(xué),非常nice的iOS開發(fā)。樓主為你點(diǎn)個(gè)贊。那么相信你也不吝嗇手指動(dòng)一動(dòng),給本文點(diǎn)個(gè)喜歡...順便關(guān)注一下樓主...畢竟寫了這么多...也很辛苦...咳咳,我不小心說出心聲了么?
  • 最后,萬一如果本文有人轉(zhuǎn)載,麻煩注明出處~謝謝!

參考:
iOS--AFN實(shí)現(xiàn)原理
AFNetworking到底做了什么?(終)

AFNetworking到底做了什么(二)?
AFNetworking之于https認(rèn)證
AFNetworking之UIKit擴(kuò)展與緩存實(shí)現(xiàn)
iOS 網(wǎng)絡(luò)框架-AFNetworking 3.1.0 源碼解讀

AFNetworking的基本使用

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

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

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