AFNetworking到底作了什麼?

寫在開頭:

  • 做爲一個iOS開發,也許你不知道NSUrlRequest、不知道NSUrlConnection、也不知道NSURLSession...(說不下去了...怎麼會什麼都不知道...)可是你必定知道AFNetworking。
  • 大多數人習慣了只要是請求網絡都用AF,可是你真的知道AF作了什麼嗎?爲何咱們不用原生的NSURLSession而選擇AFNetworking?
  • 本文將從源碼的角度去分析AF的實際做用。
    或許看完這篇文章,你內心會有一個答案。

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

  • 首先,咱們就一塊兒分析一下該框架的組成。
    將AF下載導入工程後,下面是其包結構,相對於2.x變得很是簡單了:

除去Support Files,能夠看到AF分爲以下5個功能模塊:

  • 網絡通訊模塊(AFURLSessionManager、AFHTTPSessionManger)
  • 網絡狀態監聽模塊(Reachability)
  • 網絡通訊安全策略模塊(Security)
  • 網絡通訊信息序列化/反序列化模塊(Serialization)
  • 對於iOS UIKit庫的擴展(UIKit)

其核心固然是網絡通訊模塊AFURLSessionManager。你們都知道,AF3.x是基於NSURLSession來封裝的。因此這個類圍繞着NSURLSession作了一系列的封裝。而其他的四個模塊,均是爲了配合網絡通訊或對已有UIKit的一個擴展工具包。php

這五個模塊所對應的類的結構關係圖以下所示:ios

其中AFHTTPSessionManager是繼承於AFURLSessionManager的,咱們通常作網絡請求都是用這個類, 可是它自己是沒有作實事的,只是作了一些簡單的封裝,把請求邏輯分發給父類AFURLSessionManager或者其它類去作。

首先咱們簡單的寫個get請求:

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) {
    
}];
複製代碼

首先咱們咱們調用了初始化方法生成了一個manager,咱們點進去看看初始化作了什麼:git

- (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;
    }
    //對傳過來的BaseUrl進行處理,若是有值且最後不包含/,url加上"/"
  //--經一位熱心讀者更正...之後註釋也必定要走心啊...不能誤導你們...
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }

    self.baseURL = url;

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

    return self;
}
複製代碼
  • 初始化都調用到- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration方法中來了。github

  • 其實初始化方法都調用父類的初始化方法。父類也就是AF3.x最最核心的類AFURLSessionManager。幾乎全部的類都是圍繞着這個類在處理業務邏輯。web

  • 除此以外,方法中把baseURL存了起來,還生成了一個請求序列對象和一個響應序列對象。後面再細說這兩個類是幹什麼用的。編程

直接來到父類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併發線程數設置爲1
    self.operationQueue.maxConcurrentOperationCount = 1;
    
    //注意代理,代理的繼承,實際上NSURLSession去判斷了,你實現了哪一個方法會去調用,包括子代理的方法!
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
    
    //各類響應轉碼
    self.responseSerializer = [AFJSONResponseSerializer serializer];

    //設置默認安全策略
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

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

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

    // 置空task關聯的代理
    [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;
}
複製代碼
  • 這個就是最終的初始化方法了,註釋應該寫的很清楚,惟一須要說的就是三點:數組

    • self.operationQueue.maxConcurrentOperationCount = 1;這個operationQueue就是咱們代理回調的queue。這裏把代理回調的線程併發數設置爲1了。至於這裏爲何要這麼作,咱們先留一個坑,等咱們講完AF2.x以後再來分析這一塊。
  • 第二就是咱們初始化了一些屬性,其中包括
    self.mutableTaskDelegatesKeyedByTaskIdentifier,這個是用來讓每個請求task和咱們自定義的AF代理來創建映射用的,其實AF對task的代理進行了一個封裝,而且轉發代理到AF自定義的代理,這是AF比較重要的一部分,接下來咱們會具體講這一塊。緩存

  • 第三就是下面這個方法:安全

[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { 
}];
複製代碼

首先說說這個方法是幹什麼用的:這個方法用來異步的獲取當前session的全部未完成的task。其實講道理來講在初始化中調用這個方法應該裏面一個task都不會有。咱們打斷點去看,也確實如此,裏面的數組都是空的。 可是想一想也知道,AF大神不會把一段沒用的代碼放在這吧。展轉多處,終於從AF的issue中找到告終論:githubbash

  • 原來這是爲了防止後臺回來,從新初始化這個session,一些以前的後臺請求任務,致使程序的crash。

初始化方法到這就所有完成了。

小編這呢,給你們推薦一個優秀的iOS交流平臺,平臺裏的夥伴們都是很是優秀的iOS開發人員,咱們專一於技術的分享與技巧的交流,你們能夠在平臺上討論技術,交流學習。歡迎你們的加入(想要加入的可加小編微信15673450590)。

接着咱們來看看網絡請求:

- (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
{
     //生成一個task
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];
  
    //開始網絡請求
    [dataTask resume];

    return dataTask;
}
複製代碼

方法走到類AFHTTPSessionManager中來,調用父類,也就是咱們整個AF3.x的核心類AFURLSessionManager的方法,生成了一個系統的NSURLSessionDataTask實例,而且開始網絡請求。

咱們繼續往父類裏看,看看這個方法到底作了什麼:

- (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;
    
    //把參數,還有各類東西轉化爲一個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"
            //若是解析錯誤,直接返回
            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;
}
複製代碼
  • 這個方法作了兩件事:
    1.用self.requestSerializer和各類參數去獲取了一個咱們最終請求網絡須要的NSMutableURLRequest實例。

    2.調用另一個方法dataTaskWithRequest去拿到咱們最終須要的NSURLSessionDataTask實例,而且在完成的回調裏,調用咱們傳過來的成功和失敗的回調。

  • 注意下面這個方法,咱們經常使用來 push pop搭配,來忽略一些編譯器的警告:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop
複製代碼

這裏是用來忽略:?帶來的警告,具體的各類編譯器警告描述,能夠參考這篇:各類編譯器的警告

  • 說到底這個方法仍是沒有作實事,咱們繼續到requestSerializer方法裏去看,看看AF到底如何拼接成咱們須要的request的:

接着咱們跑到AFURLRequestSerialization類中:

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    //斷言,debug模式下,若是缺乏改參數,crash
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];

    NSParameterAssert(url);

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

    //將request的各類屬性循環遍歷
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        //若是本身觀察到的發生變化的屬性,在這些方法裏
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
           //把給本身設置的屬性給request設置
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }
    //將傳入的parameters進行編碼,並添加到request中
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

    return mutableRequest;
}
複製代碼
  • 講一下這個方法,這個方法作了3件事:
    1)設置request的請求類型,get,post,put...等
    2)往request裏添加一些參數設置,其中AFHTTPRequestSerializerObservedKeyPaths()是一個c函數,返回一個數組,咱們來看看這個函數:
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))];
    });
    //就是一個數組裏裝了不少方法的名字,
    return _AFHTTPRequestSerializerObservedKeyPaths;
}
複製代碼

其實這個函數就是封裝了一些屬性的名字,這些都是NSUrlRequest的屬性。 再來看看self.mutableObservedChangedKeyPaths,這個是當前類的一個屬性:

@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
複製代碼

在-init方法對這個集合進行了初始化,而且對當前類的和NSUrlRequest相關的那些屬性添加了KVO監聽

//每次都會重置變化
    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觸發的方法:

-(void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(__unused id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    //當觀察到這些set方法被調用了,並且不爲Null就會添加到集合裏,不然移除
    if (context == AFHTTPRequestSerializerObserverContext) {
        if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
            [self.mutableObservedChangedKeyPaths removeObject:keyPath];
        } else {
            [self.mutableObservedChangedKeyPaths addObject:keyPath];
        }
    }
}
複製代碼

至此咱們知道self.mutableObservedChangedKeyPaths其實就是咱們本身設置的request屬性值的集合。

接下來調用:

[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
複製代碼

用KVC的方式,把屬性值都設置到咱們請求的request中去。

3)把須要傳遞的參數進行編碼,而且設置到request中去:

//將傳入的parameters進行編碼,並添加到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裏去遍歷,若是有值則設置給request的head
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    //來把各類類型的參數,array dic set轉化成字符串,給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 {
            //默認解析方式
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    //最後判斷該request中是否包含了GET、HEAD、DELETE(都包含在HTTPMethodsEncodingParametersInURI)。由於這幾個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請求
        
        // #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"];
        }
        //設置請求體
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }

    return mutableRequest;
}
複製代碼

這個方法作了3件事:
1.從self.HTTPRequestHeaders中拿到設置的參數,賦值要請求的request裏去
2.把請求網絡的參數,從array dic set這些容器類型轉換爲字符串,具體轉碼方式,咱們可使用自定義的方式,也能夠用AF默認的轉碼方式。自定義的方式沒什麼好說的,想怎麼去解析由你本身來決定。咱們能夠來看看默認的方式:

NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    
    //把參數給AFQueryStringPairsFromDictionary,拿到AF的一個類型的數據就一個key,value對象,在URLEncodedStringValue拼接keyValue,一個加到數組裏
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }

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

    // 根據須要排列的對象的description來進行升序排列,而且selector使用的是compare:
    // 由於對象的description返回的是NSString,因此此處compare:使用的是NSString的compare函數
    // 即@[@"foo", @"bar", @"bae"] ----> @[@"bae", @"bar",@"foo"]
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    //判斷vaLue是什麼類型的,而後去遞歸調用本身,直到解析的是除了array dic set之外的元素,而後把獲得的參數數組返回。
    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;
}
複製代碼
  • 轉碼主要是以上三個函數,配合着註釋應該也很好理解:主要是在遞歸調用AFQueryStringPairsFromKeyAndValue。判斷vaLue是什麼類型的,而後去遞歸調用本身,直到解析的是除了array dic set之外的元素,而後把獲得的參數數組返回。
  • 其中有個AFQueryStringPair對象,其只有兩個屬性和兩個方法:
@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])];
    }
}
複製代碼

方法很簡單,如今咱們也很容易理解這整個轉碼過程了,咱們舉個例子梳理下,就是如下這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
複製代碼

至此,咱們原來的容器類型的參數,就這樣變成字符串類型了。

緊接着這個方法還根據該request中請求類型,來判斷參數字符串應該如何設置到request中去。若是是GET、HEAD、DELETE,則把參數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請求
    
    // #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"];
    }
    //設置請求體
    [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
複製代碼

至此,咱們生成了一個request。

小編這呢,給你們推薦一個優秀的iOS交流平臺,平臺裏的夥伴們都是很是優秀的iOS開發人員,咱們專一於技術的分享與技巧的交流,你們能夠在平臺上討論技術,交流學習。歡迎你們的加入(想要加入的可加小編微信15673450590)。

咱們再回到AFHTTPSessionManager類中來,回到這個方法:

- (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;
    //把參數,還有各類東西轉化爲一個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"
            //若是解析錯誤,直接返回
            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;
}
複製代碼

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

  • 咱們繼續往下看:當解析錯誤,咱們直接調用傳進來的fauler的Block失敗返回了,這裏有一個self.completionQueue,這個是咱們自定義的,這個是一個GCD的Queue若是設置了那麼從這個Queue中回調結果,不然從主隊列回調。
  • 實際上這個Queue仍是挺有用的,以前還用到過。咱們公司有本身的一套數據加解密的解析模式,因此咱們回調回來的數據並不想是主線程,咱們能夠設置這個Queue,在分線程進行解析數據,而後本身再調回到主線程去刷新UI。

言歸正傳,咱們接着調用了父類的生成task的方法,而且執行了一個成功和失敗的回調,咱們接着去父類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;
    //第一件事,建立NSURLSessionDataTask,裏面適配了Ios8如下taskIdentifiers,函數建立task對象。
    //其實現應該是由於iOS 8.0如下版本中會併發地建立多個task對象,而同步有沒有作好,致使taskIdentifiers 不惟一…這邊作了一個串行處理
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });

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

    return dataTask;
}
複製代碼
  • 咱們注意到這個方法很是簡單,就調用了一個url_session_manager_create_task_safely()函數,傳了一個Block進去,Block裏就是iOS原生生成dataTask的方法。此外,還調用了一個addDelegateForDataTask的方法。
  • 咱們到這先到這個函數裏去看看:
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,由於是想要主線程等在這,等執行完,在返回,由於必須執行完dataTask纔有數據,傳值纔有意義。
      //第二,爲何要用串行隊列,由於這塊是爲了防止ios8如下內部的dataTaskWithRequest是併發建立的,
      //這樣會致使taskIdentifiers這個屬性值不惟一,由於後續要用taskIdentifiers來做爲Key對應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;
    //保證了即便是在多線程的環境下,也不會建立其餘隊列
    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;
}
複製代碼
  • 方法很是簡單,關鍵是理解這麼作的目的:爲何咱們不直接去調用 dataTask = [self.session dataTaskWithRequest:request]; 非要繞這麼一圈,咱們點進去bug日誌裏看看,原來這是爲了適配iOS8的如下,建立session的時候,偶發的狀況會出現session的屬性taskIdentifier這個值不惟一,而這個taskIdentifier是咱們後面來映射delegate的key,因此它必須是惟一的。

  • 具體緣由應該是NSURLSession內部去生成task的時候是用多線程併發去執行的。想通了這一點,咱們就很好解決了,咱們只須要在iOS8如下同步串行的去生成task就能夠防止這一問題發生(若是仍是不理解同步串行的緣由,能夠看看註釋)。

  • 題外話:不少同窗都會抱怨爲何sync我歷來用不到,看,有用到的地方了吧,不少東西不是沒用,而只是你想不到怎麼用

咱們接着看到:

[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
複製代碼

調用到:

- (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創建相互關係
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    //這個taskDescriptionForSessionTasks用來發送開始和掛起通知的時候會用到,就是用這個值來Post通知,來二者對應
    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    
    // ***** 將AF delegate對象與 dataTask創建關係
    [self setDelegate:delegate forTask:dataTask];

    // 設置AF delegate的上傳進度,下載進度塊。
    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}
複製代碼
  • 總結一下:
    1)這個方法,生成了一個AFURLSessionManagerTaskDelegate,這個其實就是AF的自定義代理。咱們請求傳來的參數,都賦值給這個AF的代理了。
    2)delegate.manager = self;代理把AFURLSessionManager這個類做爲屬性了,咱們能夠看到:
@property (nonatomic, weak) AFURLSessionManager *manager;
複製代碼

這個屬性是弱引用的,因此不會存在循環引用的問題。
3)咱們調用了[self setDelegate:delegate forTask:dataTask];

咱們進去看看這個方法作了什麼:

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    //斷言,若是沒有這個參數,debug下crash在這
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    //加鎖保證字典線程安全
    [self.lock lock];
    // 將AF delegate放入以taskIdentifier標記的詞典中(同一個NSURLSession中的taskIdentifier是惟一的)
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
   
    // 爲AF delegate 設置task 的progress監聽
    [delegate setupProgressForTask:task];
    
    //添加task開始和暫停的通知
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}
複製代碼
  • 這個方法主要就是把AF代理和task創建映射,存在了一個咱們事先聲明好的字典裏。
  • 而要加鎖的緣由是由於自己咱們這個字典屬性是mutable的,是線程不安全的。而咱們對這些方法的調用,確實是會在複雜的多線程環境中,後面會仔細提到線程問題。
  • 還有個[delegate setupProgressForTask:task];咱們到方法裏去看看:
- (void)setupProgressForTask:(NSURLSessionTask *)task {
    
    __weak __typeof__(task) weakTask = task;

    //拿到上傳下載指望的數據大小
    self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
    self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
    
    
    //將上傳與下載進度和 任務綁定在一塊兒,直接cancel suspend resume進度條,能夠cancel...任務
    [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這兩個屬性
    [self.downloadProgress addObserver:self
                            forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                               options:NSKeyValueObservingOptionNew
                               context:NULL];
    [self.uploadProgress addObserver:self
                          forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
}
複製代碼
  • 這個方法也很是簡單,主要作了如下幾件事:
    1)設置 downloadProgress與uploadProgress的一些屬性,而且把二者和task的任務狀態綁定在了一塊兒。注意這二者都是NSProgress的實例對象,(這裏可能又一羣小夥伴楞在這了,這是個什麼...)簡單來講,這就是iOS7引進的一個用來管理進度的類,能夠開始,暫停,取消,完整的對應了task的各類狀態,當progress進行各類操做的時候,task也會引起對應操做。
    2)給task和progress的各個屬及添加KVO監聽,至於監聽了幹什麼用,咱們接着往下看:
- (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]]) {
        //給進度條賦新值
        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];
        }
    }
    //上面的賦新值會觸發這兩個,調用block回調,用戶拿到進度
    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觸發KVO,則給progress進度賦值,應爲賦值了,因此會觸發progress的KVO,也會調用到這裏,而後去執行咱們傳進來的downloadProgressBlockuploadProgressBlock。主要的做用就是爲了讓進度實時的傳遞。
  • 主要是觀摩一下大神的寫代碼的結構,這個解耦的編程思想,不愧是大神...
  • 還有一點須要注意:咱們以前的setProgress和這個KVO監聽,都是在咱們AF自定義的delegate內的,是有一個task就會有一個delegate的。因此說咱們是每一個task都會去監聽這些屬性,分別在各自的AF代理內。看到這,可能有些小夥伴會有點亂,不要緊。等整個講完以後咱們還會詳細的去講捋一捋manager、task、還有AF自定義代理三者以前的對應關係。

到這裏咱們整個對task的處理就完成了。

小編這呢,給你們推薦一個優秀的iOS交流平臺,平臺裏的夥伴們都是很是優秀的iOS開發人員,咱們專一於技術的分享與技巧的交流,你們能夠在平臺上討論技術,交流學習。歡迎你們的加入(想要加入的可加小編微信15673450590)。

接着task就開始請求網絡了,還記得咱們初始化方法中:

self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
複製代碼

咱們把AFUrlSessionManager做爲了全部的task的delegate。當咱們請求網絡的時候,這些代理開始調用了:

  • AFUrlSessionManager一共實現瞭如上圖所示這麼一大堆NSUrlSession相關的代理。(小夥伴們的順序可能不同,樓主根據代理隸屬從新排序了一下)

  • 而只轉發了其中3條到AF自定義的delegate中:

這就是咱們一開始說的,AFUrlSessionManager對這一大堆代理作了一些公共的處理,而轉發到AF自定義代理的3條,則負責把每一個task對應的數據回調出去。

又有小夥伴問了,咱們設置的這個代理不是NSURLSessionDelegate嗎?怎麼能響應NSUrlSession這麼多代理呢?咱們點到類的聲明文件中去看看:

@protocol NSURLSessionDelegate <NSObject>
@protocol NSURLSessionTaskDelegate <NSURLSessionDelegate>
@protocol NSURLSessionDataDelegate <NSURLSessionTaskDelegate>
@protocol NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
@protocol NSURLSessionStreamDelegate <NSURLSessionTaskDelegate>
複製代碼
  • 咱們能夠看到這些代理都是繼承關係,而在NSURLSession實現中,只要設置了這個代理,它會去判斷這些全部的代理,是否respondsToSelector這些代理中的方法,若是響應了就會去調用。
  • 而AF還重寫了respondsToSelector方法:
- (BOOL)respondsToSelector:(SEL)selector {
    
    //複寫了selector的方法,這幾個方法是在本類有實現的,可是若是外面的Block沒賦值的話,則返回NO,至關於沒有實現!
    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];
}
複製代碼

這樣若是沒實現這些咱們自定義的Block也不會去回調這些代理。由於自己某些代理,只執行了這些自定義的Block,若是Block都沒有賦值,那咱們調用代理也沒有任何意義。 講到這,咱們順便看看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;
複製代碼

各自對應的還有一堆這樣的set方法:

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

方法都是同樣的,就不重複粘貼佔篇幅了。
主要談談這個設計思路

  • 做者用@property把這個些Block屬性在.m文件中聲明,而後複寫了set方法。
  • 而後在.h中去聲明這些set方法:
- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;
複製代碼

爲何要繞這麼一大圈呢?原來這是爲了咱們這些用戶使用起來方便,調用set方法去設置這些Block,能很清晰的看到Block的各個參數與返回值。大神的精髓的編程思想無處不體現...

接下來咱們就講講這些代理方法作了什麼(按照順序來):

NSURLSessionDelegate

代理1:

//當前這個session已經失效時,該代理方法被調用。
/*
 若是你使用finishTasksAndInvalidate函數使該session失效,
 那麼session首先會先完成最後一個task,而後再調用URLSession:didBecomeInvalidWithError:代理方法,
 若是你調用invalidateAndCancel方法來使session失效,那麼該session會當即調用上面的代理方法。
 */
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
    if (self.sessionDidBecomeInvalid) {
        self.sessionDidBecomeInvalid(session, error);
    }

    [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}
複製代碼
  • 方法調用時機註釋寫的很清楚,就調用了一下咱們自定義的Block,還發了一個失效的通知,至於這個通知有什麼用。很抱歉,AF沒用它作任何事,只是發了...目的是用戶本身能夠利用這個通知作什麼事吧。
  • 其實AF大部分通知都是如此。固然,還有一部分通知AF仍是有本身用到的,包括配合對UIKit的一些擴展來使用,後面咱們會有單獨篇幅展開講講這些UIKit的擴展類的實現。

代理2:

//二、https認證
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    //挑戰處理類型爲 默認
    /*
     NSURLSessionAuthChallengePerformDefaultHandling:默認方式處理
     NSURLSessionAuthChallengeUseCredential:使用指定的證書
     NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰
     */
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    // sessionDidReceiveAuthenticationChallenge是自定義方法,用來如何應對服務器端的認證挑戰

    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        // 此處服務器要求客戶端的接收認證挑戰方法是NSURLAuthenticationMethodServerTrust
        // 也就是說服務器端須要客戶端返回一個根據認證挑戰的保護空間提供的信任(即challenge.protectionSpace.serverTrust)產生的挑戰證書。
       
        // 而這個證書就須要使用credentialForTrust:來建立一個NSURLCredential對象
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            
            // 基於客戶端的安全策略來決定是否信任該服務器,不信任的話,也就不必響應挑戰
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
               
                // 建立挑戰證書(注:挑戰方式爲UseCredential和PerformDefaultHandling都須要新建挑戰證書)
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                // 肯定挑戰的方式
                if (credential) {
                    //證書挑戰
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    //默認挑戰  惟一區別,下面少了這一步!
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                //取消挑戰
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            //默認挑戰方式
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }
    //完成挑戰
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

複製代碼
  • 函數做用: web服務器接收到客戶端請求時,有時候須要先驗證客戶端是否爲正經常使用戶,再決定是夠返回真實數據。這種狀況稱之爲服務端要求客戶端接收挑戰(NSURLAuthenticationChallenge *challenge)。接收到挑戰後,客戶端要根據服務端傳來的challenge來生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential(disposition指定應對這個挑戰的方法,而credential是客戶端生成的挑戰證書,注意只有challenge中認證方法爲NSURLAuthenticationMethodServerTrust的時候,才須要生成挑戰證書)。最後調用completionHandler迴應服務器端的挑戰。
  • 函數討論:
    該代理方法會在下面兩種狀況調用:
  1. 當服務器端要求客戶端提供證書時或者進行NTLM認證(Windows NT LAN Manager,微軟提出的WindowsNT挑戰/響應驗證機制)時,此方法容許你的app提供正確的挑戰證書。
  2. 當某個session使用SSL/TLS協議,第一次和服務器端創建鏈接的時候,服務器會發送給iOS客戶端一個證書,此方法容許你的app驗證服務期端的證書鏈(certificate keychain) 注:若是你沒有實現該方法,該session會調用其NSURLSessionTaskDelegate的代理方法URLSession:task:didReceiveChallenge:completionHandler: 。

這裏,我把官方文檔對這個方法的描述翻譯了一下。
總結一下,這個方法其實就是作https認證的。看看上面的註釋,大概能看明白這個方法作認證的步驟,咱們仍是若是有自定義的作認證的Block,則調用咱們自定義的,不然去執行默認的認證步驟,最後調用完成認證:

//完成挑戰 
if (completionHandler) { 
      completionHandler(disposition, credential); 
}
複製代碼

代理3:

//三、 當session中全部已經入隊的消息被髮送出去後,會調用該代理方法。
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    if (self.didFinishEventsForBackgroundURLSession) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.didFinishEventsForBackgroundURLSession(session);
        });
    }
}
複製代碼

官方文檔翻譯:

函數討論:

  • 在iOS中,當一個後臺傳輸任務完成或者後臺傳輸時須要證書,而此時你的app正在後臺掛起,那麼你的app在後臺會自動從新啓動運行,而且這個app的UIApplicationDelegate會發送一個application:handleEventsForBackgroundURLSession:completionHandler:消息。該消息包含了對應後臺的session的identifier,並且這個消息會致使你的app啓動。你的app隨後應該先存儲completion handler,而後再使用相同的identifier建立一個background configuration,並根據這個background configuration建立一個新的session。這個新建立的session會自動與後臺任務從新關聯在一塊兒。
  • 當你的app獲取了一個URLSessionDidFinishEventsForBackgroundURLSession:消息,這就意味着以前這個session中已經入隊的全部消息都轉發出去了,這時候再調用先前存取的completion handler是安全的,或者由於內部更新而致使調用completion handler也是安全的。

NSURLSessionTaskDelegate

代理4:

//被服務器重定向的時候調用
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
        newRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLRequest *))completionHandler
{
    NSURLRequest *redirectRequest = request;

    // step1. 看是否有對應的user block 有的話轉發出去,經過這4個參數,返回一個NSURLRequest類型參數,request轉發、網絡重定向.
    if (self.taskWillPerformHTTPRedirection) {
        //用本身自定義的一個重定向的block實現,返回一個新的request。
        redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
    }

    if (completionHandler) {
        // step2. 用request從新請求
        completionHandler(redirectRequest);
    }
}
複製代碼
  • 一開始我覺得這個方法是相似NSURLProtocol,能夠在請求時本身主動的去重定向request,後來發現不是,這個方法是在服務器去重定向的時候,纔會被調用。爲此我寫了段簡單的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/");
    }
}
複製代碼

證明確實如此,當咱們服務器重定向的時候,代理就被調用了,咱們能夠去從新定義這個重定向的request。

  • 關於這個代理還有一些須要注意的地方:

此方法只會在default session或者ephemeral session中調用,而在background session中,session task會自動重定向。

這裏指的模式是咱們一開始Init的模式:

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

這個模式總共分爲3種:

對於NSURLSession對象的初始化須要使用NSURLSessionConfiguration,而NSURLSessionConfiguration有三個類工廠方法: +defaultSessionConfiguration 返回一個標準的 configuration,這個配置實際上與 NSURLConnection 的網絡堆棧(networking stack)是同樣的,具備相同的共享 NSHTTPCookieStorage,共享 NSURLCache 和共享NSURLCredentialStorage。 +ephemeralSessionConfiguration 返回一個預設配置,這個配置中不會對緩存,Cookie 和證書進行持久性的存儲。這對於實現像祕密瀏覽這種功能來講是很理想的。 +backgroundSessionConfiguration:(NSString *)identifier 的獨特之處在於,它會建立一個後臺 session。後臺 session 不一樣於常規的,普通的 session,它甚至能夠在應用程序掛起,退出或者崩潰的狀況下運行上傳和下載任務。初始化時指定的標識符,被用於向任何可能在進程外恢復後臺傳輸的守護進程(daemon)提供上下文。

代理5:

//https認證
- (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);
    }
}
複製代碼
  • 鑑於篇幅,就不去貼官方文檔的翻譯了,大概總結一下: 以前咱們也有一個https認證,功能同樣,執行的內容也徹底同樣。
  • 區別在於這個是non-session-level級別的認證,而以前的是session-level級別的。
  • 相對於它,多了一個參數task,而後調用咱們自定義的Block會多回傳這個task做爲參數,這樣咱們就能夠根據每一個task去自定義咱們須要的https認證方式。

代理6:

//當一個session task須要發送一個新的request body stream到服務器端的時候,調用該代理方法。

- (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);
    }
}
複製代碼
  • 該代理方法會在下面兩種狀況被調用:
    1. 若是task是由uploadTaskWithStreamedRequest:建立的,那麼提供初始的request body stream時候會調用該代理方法。
    2. 由於認證挑戰或者其餘可恢復的服務器錯誤,而致使須要客戶端從新發送一個含有body stream的request,這時候會調用該代理。

代理7:

/*
 //週期性地通知代理髮送到服務器端數據的進度。
 */

- (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);
    }
}
複製代碼
  • 就是每次發送數據給服務器,會回調這個方法,通知已經發送了多少,總共要發送多少。
  • 代理方法裏也就是僅僅調用了咱們自定義的Block而已。

未完總結:

  • 其實寫了這麼多,尚未講到真正重要的地方,可是由於已經接近簡書最大篇幅,因此只能先在這裏結個尾了。
  • 若是能看到這裏,說明你是個很是有耐心,很是好學,很是nice的iOS開發。樓主爲你點個贊。那麼相信你也不吝嗇手指動一動,給本文點個喜歡...順便關注一下樓主...畢竟寫了這麼多...也很辛苦...咳咳,我不當心說出心聲了麼?

轉載於做者:塗耀輝
連接:www.jianshu.com/p/856f0e262…

相關文章
相關標籤/搜索