iOS之AFNetworking3.1.0源碼解讀

AFNetworking基本每個項目都會用。但是看它的代碼的人不多。有一次面試冀宴,面試官問我看過AFNetworking的源碼沒?我說沒有嚎杨,他說了句不看源碼怎么提高花鹅?然后接著一句你們年輕人太浮躁了氧腰。雖然他說的我當時心里挺不爽枫浙,但是人家說的很有道理」潘苦口良藥箩帚。項目不忙的時候,我會看會兒黄痪,把看到的和學到的總結下紧帕。方便對網(wǎng)絡有個更深的理解。AFNetworking下載地址https://github.com/AFNetworking/AFNetworking/tree/3.1.0

結構

3C7DBBA3-2571-4F97-8322-6CE8707DA01B.png

可以看到AF分為如下5個功能模塊:

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

其核心當然是網(wǎng)絡通信模塊AFURLSessionManager是嗜。大家都知道,AF3.1是基于NSURLSession來封裝的挺尾。所以這個類圍繞著NSURLSession做了一系列的封裝鹅搪。而其余的四個模塊,均是為了配合網(wǎng)絡通信或對已有UIKit的一個擴展工具包遭铺。
這五個模塊所對應的類的結構關系圖如下所示:

2702646-10294db19b1aedfd.png.jpeg

其中AFHTTPSessionManager是繼承于AFURLSessionManager的丽柿,我們一般做網(wǎng)絡請求都是用這個類恢准,但是它本身是沒有做實事的,只是做了一些簡單的封裝甫题,把請求邏輯分發(fā)給父類AFURLSessionManager或者其它類去做馁筐。

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

AFHTTPSessionManager  *manager = [AFHTTPSessionManager manager];
    
    [manager GET:@"http://www.baidu.com" parameters:@"df" progress:^(NSProgress * _Nonnull downloadProgress) {
        
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        
    }];

首先我們我們調用了初始化方法生成了一個manager,我們點進去看看初始化做了什么:

+ (instancetype)manager {
    return [[[self class] alloc] initWithBaseURL:nil];
}

- (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;
    }

    //    scheme://host.domain:port/path/filename
    //    
    //    scheme - 定義因特網(wǎng)服務的類型坠非。最常見的類型是 http
    //    host - 定義域主機(http 的默認主機是 www)
    //    domain - 定義因特網(wǎng)域名敏沉,比如 w3school.com.cn
    //    :port - 定義主機上的端口號(http 的默認端口號是 80)
    //    path - 定義服務器上的路徑(如果省略,則文檔必須位于網(wǎng)站的根目錄中)炎码。
    //    filename - 定義文檔/資源的名稱
    
    //對傳過來的BaseUrl進行處理赦抖,如果path有值且最后不包含/,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方法來了辅肾。其實初始化方法都調用父類的初始化方法队萤。父類也就是最最核心的類AFURLSessionManager。幾乎所有的類都是圍繞著這個類在處理業(yè)務邏輯矫钓。除此之外要尔,方法中把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并發(fā)線程數(shù)設置為1
    self.operationQueue.maxConcurrentOperationCount = 1;
    //注意代理,代理的繼承概龄,實際上NSURLSession去判斷了还惠,你實現(xiàn)了哪個方法會去調用,包括子代理的方法私杜!
    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關聯(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;
}

這個就是最終的初始化方法了踪危,注釋應該寫的很清楚,唯一需要說的就是三點:
self.operationQueue.maxConcurrentOperationCount = 1;這個operationQueue就是我們代理回調的queue铝耻。這里把代理回調的線程并發(fā)數(shù)設置為1了誊爹。至于這里為什么要這么做,我們先留一個坑瓢捉,等我們講完AF2.x之后再來分析這一塊频丘。
第二就是我們初始化了一些屬性,其中包括self.mutableTaskDelegatesKeyedByTaskIdentifier泡态,這個是用來讓每一個請求task和我們自定義的AF代理來建立映射用的搂漠,其實AF對task的代理進行了一個封裝,并且轉發(fā)代理到AF自定義的代理兽赁,這是AF比較重要的一部分状答,接下來我們會具體講這一塊冷守。
第三就是下面這個方法:

- (void)getTasksWithCompletionHandler:(void (^)(NSArray<NSURLSessionDataTask *> *dataTasks, NSArray<NSURLSessionUploadTask *> *uploadTasks, NSArray<NSURLSessionDownloadTask *> *downloadTasks))completionHandler; 

首先說說這個方法是干什么用的:這個方法用來異步的獲取當前session的所有未完成的task。其實講道理來說在初始化中調用這個方法應該里面一個task都不會有惊科。我們打斷點去看拍摇,也確實如此,里面的數(shù)組都是空的馆截。但是想想也知道充活,AF大神不會把一段沒用的代碼放在這吧。輾轉多處蜡娶,終于從AF的issue中找到了結論:github 混卵。
原來這是為了從后臺回來,重新初始化session窖张,防止一些之前的后臺請求任務幕随,導致程序的crash。

接著我們來看看網(wǎ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
{

  //生成一個task
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];
    // 開始網(wǎng)絡請求
    [dataTask resume];

    return dataTask;
}

方法走到類AFHTTPSessionManager中來宿接,調用父類赘淮,也就是我們整個AF3.1的核心類AFURLSessionManager的方法,生成了一個系統(tǒng)的NSURLSessionDataTask實例睦霎,并且開始網(wǎng)絡請求梢卸。
我們繼續(xù)往父類里看,看看這個方法到底做了什么:

- (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
{
    //1.首先創(chuàng)建一個NSMutableURLRequest對象的實例
    //[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString]
    //如果self.baseURL不為空副女,那么就是拼接如baseurl + urlsting
    //如果self.baseURL為空蛤高,那么就是urlString本身了。
    //absoluteString 是將url轉化為字符串
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    if (serializationError) {
        if (failure) {
            //下面的宏定義是為了消除代碼的警告http://www.reibang.com/p/3c7a4feaee16
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic popx
        }

        return nil;
    }
    
   // 2.創(chuàng)建NSURLSessionDataTask對象實例
    __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和各種參數(shù)去獲取了一個我們最終請求網(wǎng)絡需要的NSMutableURLRequest實例碑幅。
2.調用另外一個方法dataTaskWithRequest去拿到我們最終需要的NSURLSessionDataTask實例戴陡,并且在完成的回調里,調用我們傳過來的成功和失敗的回調枕赵。
接著我們跑到AFURLRequestSerialization類中:

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    //斷言猜欺,看看傳的參數(shù)是不是nil位隶,如果是nil直接奔潰拷窜。Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: URLString'
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];
 
    NSParameterAssert(url);

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    //設置請求的方法是get或者post
    mutableRequest.HTTPMethod = method;
    
    //allowsCellularAccess 允許使用數(shù)據(jù)流量
    //cachePolicy 緩存策略
    //HTTPShouldHandleCookies 處理Cookie
    //HTTPShouldUsePipelining 批量請求
    //networkServiceType 網(wǎng)絡狀態(tài)
    //timeoutInterval 超時
    //用kvc的方式為mutableRequest的上面的屬性賦值。
    //AFHTTPRequestSerializerObservedKeyPaths()是一個c語言函數(shù)
    //將request的各種屬性循環(huán)遍歷
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        //如果自己觀察到的發(fā)生變化的屬性涧黄,在這些方法里
        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里添加一些參數(shù)設置懊昨,其中AFHTTPRequestSerializerObservedKeyPaths()是一個c函數(shù),返回一個數(shù)組春宣,我們來看看這個函數(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))];
  });
  //就是一個數(shù)組里裝了很多方法的名字,
  return _AFHTTPRequestSerializerObservedKeyPaths;
}

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

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

在-init方法對這個集合進行了初始化簿姨,并且對當前類的和NSUrlRequest相關的那些屬性添加了KVO監(jiān)聽:

//每次都會重置變化
  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
{
    //當觀察到這些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)把需要傳遞的參數(shù)進行編碼刑然,并且設置到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];
      }
  }];

  //來把各種類型的參數(shù)闰集,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中拿到設置的參數(shù)叹谁,賦值要請求的request里去
2.把請求網(wǎng)絡的參數(shù)饲梭,從array dic set這些容器類型轉換為字符串,具體轉碼方式焰檩,我們可以使用自定義的方式憔涉,也可以用AF默認的轉碼方式。自定義的方式?jīng)]什么好說的析苫,想怎么去解析由你自己來決定兜叨。我們可以來看看默認的方式:

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

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

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

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

  //判斷vaLue是什么類型的,然后去遞歸調用自己峦萎,直到解析的是除了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;
}

轉碼主要是以上三個函數(shù),配合著注釋應該也很好理解:主要是在遞歸調用AFQueryStringPairsFromKeyAndValue被环。判斷vaLue是什么類型的雄卷,然后去遞歸調用自己,直到解析的是除了array dic set以外的元素蛤售,然后把得到的參數(shù)數(shù)組返回丁鹉。
其中有個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])];
}
}

方法很簡單悴能,現(xiàn)在我們也很容易理解這整個轉碼過程了揣钦,我們舉個例子梳理下,就是以下這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ù)冯凹,就這樣變成字符串類型了。
緊接著這個方法還根據(jù)該request中請求類型炒嘲,來判斷參數(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請求

  // #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。

我們再回到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
{
    //1.首先創(chuàng)建一個NSMutableURLRequest對象的實例
    //[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString]
    //如果self.baseURL不為空鸽扁,那么就是拼接如baseurl + urlsting
    //如果self.baseURL為空蒜绽,那么就是urlString本身了。
    //absoluteString 是將url轉化為字符串
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    if (serializationError) {
        if (failure) {
            //下面的宏定義是為了消除代碼的警告http://www.reibang.com/p/3c7a4feaee16
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic popx
        }

        return nil;
    }
    
   // 2.創(chuàng)建NSURLSessionDataTask對象實例
    __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ù)往下看:當解析錯誤桶现,我們直接調用傳進來的fauler的Block失敗返回了躲雅,這里有一個self.completionQueue,這個是我們自定義的,這個是一個GCD的Queue如果設置了那么從這個Queue中回調結果骡和,否則從主隊列回調相赁。

實際上這個Queue還是挺有用的,之前還用到過即横。我們公司有自己的一套數(shù)據(jù)加解密的解析模式噪生,所以我們回調回來的數(shù)據(jù)并不想是主線程,我們可以設置這個Queue,在分線程進行解析數(shù)據(jù)东囚,然后自己再調回到主線程去刷新UI。
言歸正傳战授,我們接著調用了父類的生成task的方法页藻,并且執(zhí)行了一個成功和失敗的回調桨嫁,我們接著去父類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對象。
    //其實現(xiàn)應該是因為iOS 8.0以下版本中會并發(fā)地創(chuàng)建多個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()函數(shù)噩凹,傳了一個Block進去巴元,Block里就是iOS原生生成dataTask的方法。此外驮宴,還調用了一個addDelegateForDataTask的方法逮刨。
我們先到這個函數(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堵泽,因為是想要主線程等在這修己,等執(zhí)行完,在返回迎罗,因為必須執(zhí)行完dataTask才有數(shù)據(jù)睬愤,傳值才有意義。
    //第二纹安,為什么要用串行隊列戴涝,因為這塊是為了防止ios8以下內部的dataTaskWithRequest是并發(fā)創(chuàng)建的,
    //這樣會導致taskIdentifiers這個屬性值不唯一钻蔑,因為后續(xù)要用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;
  //保證了即使是在多線程的環(huán)境下,也不會創(chuàng)建其他隊列
  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以下窗怒,創(chuàng)建session的時候映跟,偶發(fā)的情況會出現(xiàn)session的屬性taskIdentifier這個值不唯一,而這個taskIdentifier是我們后面來映射delegate的key,所以它必須是唯一的扬虚。
具體原因應該是NSURLSession內部去生成task的時候是用多線程并發(fā)去執(zhí)行的努隙。想通了這一點,我們就很好解決了辜昵,我們只需要在iOS8以下同步串行的去生成task就可以防止這一問題發(fā)生(如果還是不理解同步串行的原因荸镊,可以看看注釋)。
題外話:很多同學都會抱怨為什么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用來發(fā)送開始和掛起通知的時候會用到,就是用這個值來Post通知宛逗,來兩者對應
    dataTask.taskDescription = self.taskDescriptionForSessionTasks;

    // ***** 將AF delegate對象與 dataTask建立關系
    [self setDelegate:delegate forTask:dataTask];

    // 設置AF delegate的上傳進度,下載進度塊盾剩。
    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

總結一下:
1)這個方法雷激,生成了一個AFURLSessionManagerTaskDelegate,這個其實就是AF的自定義代理。我們請求傳來的參數(shù)告私,都賦值給這個AF的代理了屎暇。
2)delegate.manager = self;代理把AFURLSessionManager這個類作為屬性了,我們可以看到:
@property (nonatomic, weak) AFURLSessionManager *manager;
這個屬性是弱引用的,所以不會存在循環(huán)引用的問題德挣。
3)我們調用了[self setDelegate:delegate forTask:dataTask];
我們進去看看這個方法做了什么:

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

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

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

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

這個方法主要就是把AF代理和task建立映射格嗅,存在了一個我們事先聲明好的字典里番挺。
而要加鎖的原因是因為本身我們這個字典屬性是mutable的,是線程不安全的屯掖。而我們對這些方法的調用玄柏,確實是會在復雜的多線程環(huán)境中,后面會仔細提到線程問題贴铜。
還有個[delegate setupProgressForTask:task];我們到方法里去看看:

- (void)setupProgressForTask:(NSURLSessionTask *)task {

    __weak __typeof__(task) weakTask = task;

    //拿到上傳下載期望的數(shù)據(jù)大小
    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的任務狀態(tài)綁定在了一起。注意這兩者都是NSProgress的實例對象轩褐,(這里可能又一群小伙伴楞在這了椎咧,這是個什么...)簡單來說,這就是iOS7引進的一個用來管理進度的類把介,可以開始勤讽,暫停,取消拗踢,完整的對應了task的各種狀態(tài)脚牍,當progress進行各種操作的時候,task也會引發(fā)對應操作巢墅。
2)給task和progress的各個屬及添加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]]) {
      //給進度條賦新值
      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];
      }
  }
  //上面的賦新值會觸發(fā)這兩個,調用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觸發(fā)KVO,則給progress進度賦值庵芭,應為賦值了妹懒,所以會觸發(fā)progress的KVO,也會調用到這里双吆,然后去執(zhí)行我們傳進來的downloadProgressBlock和uploadProgressBlock眨唬。主要的作用就是為了讓進度實時的傳遞。
主要是觀摩一下大神的寫代碼的結構好乐,這個解耦的編程思想匾竿,不愧是大神...
還有一點需要注意:我們之前的setProgress和這個KVO監(jiān)聽,都是在我們AF自定義的delegate內的蔚万,是有一個task就會有一個delegate的岭妖。所以說我們是每個task都會去監(jiān)聽這些屬性,分別在各自的AF代理內反璃£腔牛看到這,可能有些小伙伴會有點亂淮蜈,沒關系斋攀。等整個講完之后我們還會詳細的去講捋一捋manager、task梧田、還有AF自定義代理三者之前的對應關系淳蔼。
到這里我們整個對task的處理就完成了。

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

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

我們把AFUrlSessionManager作為了所有的task的delegate鹉梨。當我們請求網(wǎng)絡的時候,這些代理開始調用了:

2702646-5a404cc7d92fb8ec.png.jpeg

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

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


2702646-e6469f92ca6a550e.png.jpeg

這就是我們一開始說的,AFUrlSessionManager對這一大堆代理做了一些公共的處理司草,而轉發(fā)到AF自定義代理的3條艰垂,則負責把每個task對應的數(shù)據(jù)回調出去。
又有小伙伴問了埋虹,我們設置的這個代理不是NSURLSessionDelegate嗎猜憎?怎么能響應NSUrlSession這么多代理呢?我們點到類的聲明文件中去看看:

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

我們可以看到這些代理都是繼承關系搔课,而在NSURLSession實現(xiàn)中胰柑,只要設置了這個代理,它會去判斷這些所有的代理,是否respondsToSelector這些代理中的方法柬讨,如果響應了就會去調用崩瓤。
而AF還重寫了respondsToSelector方法:

- (BOOL)respondsToSelector:(SEL)selector {

  //復寫了selector的方法,這幾個方法是在本類有實現(xiàn)的踩官,但是如果外面的Block沒賦值的話却桶,則返回NO,相當于沒有實現(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];
}

這樣如果沒實現(xiàn)這些我們自定義的Block也不會去回調這些代理颖系。因為本身某些代理,只執(zhí)行了這些自定義的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的各個參數(shù)與返回值蛤签。大神的精髓的編程思想無處不體現(xiàn)...

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

代理1 NSURLSessionDelegate :

//當前這個session已經(jīng)失效時,該代理方法被調用栅哀。
/*
 如果你使用finishTasksAndInvalidate函數(shù)使該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,還發(fā)了一個失效的通知沦偎,至于這個通知有什么用。很抱歉咳蔚,AF沒用它做任何事豪嚎,只是發(fā)了...目的是用戶自己可以利用這個通知做什么事吧。
其實AF大部分通知都是如此谈火。當然侈询,還有一部分通知AF還是有自己用到的,包括配合對UIKit的一些擴展來使用糯耍,后面我們會有單獨篇幅展開講講這些UIKit的擴展類的實現(xiàn)扔字。

代理2:

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

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

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

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

            // 基于客戶端的安全策略來決定是否信任該服務器扭粱,不信任的話,也就沒必要響應挑戰(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 {
                    //默認挑戰(zhàn)  唯一區(qū)別震檩,下面少了這一步琢蛤!
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                //取消挑戰(zhàn)
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            //默認挑戰(zhàn)方式
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }
    //完成挑戰(zhàn)
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

函數(shù)作用:
web服務器接收到客戶端請求時,有時候需要先驗證客戶端是否為正常用戶恳蹲,再決定是夠返回真實數(shù)據(jù)虐块。這種情況稱之為服務端要求客戶端接收挑戰(zhàn)(NSURLAuthenticationChallenge challenge)俩滥。接收到挑戰(zhàn)后嘉蕾,客戶端要根據(jù)服務端傳來的challenge來生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential credential(disposition指定應對這個挑戰(zhàn)的方法,而credential是客戶端生成的挑戰(zhàn)證書霜旧,注意只有challenge中認證方法為NSURLAuthenticationMethodServerTrust的時候错忱,才需要生成挑戰(zhàn)證書)。最后調用completionHandler回應服務器端的挑戰(zhàn)挂据。
函數(shù)討論:
該代理方法會在下面兩種情況調用:
當服務器端要求客戶端提供證書時或者進行NTLM認證(Windows NT LAN Manager以清,微軟提出的WindowsNT挑戰(zhàn)/響應驗證機制)時,此方法允許你的app提供正確的挑戰(zhàn)證書崎逃。
當某個session使用SSL/TLS協(xié)議掷倔,第一次和服務器端建立連接的時候,服務器會發(fā)送給iOS客戶端一個證書个绍,此方法允許你的app驗證服務期端的證書鏈(certificate keychain)
注:如果你沒有實現(xiàn)該方法勒葱,該session會調用其NSURLSessionTaskDelegate的代理方法URLSession:task:didReceiveChallenge:completionHandler: 。
這里巴柿,我把官方文檔對這個方法的描述翻譯了一下。
總結一下,這個方法其實就是做https認證的澄峰〔雒唬看看上面的注釋钉迷,大概能看明白這個方法做認證的步驟糠聪,我們還是如果有自定義的做認證的Block荒椭,則調用我們自定義的,否則去執(zhí)行默認的認證步驟戳杀,最后調用完成認證:

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

代理3:

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

官方文檔翻譯:

函數(shù)討論:

在iOS中傍菇,當一個后臺傳輸任務完成或者后臺傳輸時需要證書丢习,而此時你的app正在后臺掛起,那么你的app在后臺會自動重新啟動運行揽思,并且這個app的UIApplicationDelegate會發(fā)送一個application:handleEventsForBackgroundURLSession:completionHandler:消息钉汗。該消息包含了對應后臺的session的identifier鲤屡,而且這個消息會導致你的app啟動酒来。你的app隨后應該先存儲completion handler,然后再使用相同的identifier創(chuàng)建一個background configuration辽社,并根據(jù)這個background configuration創(chuàng)建一個新的session爹袁。這個新創(chuàng)建的session會自動與后臺任務重新關聯(lián)在一起矮固。
當你的app獲取了一個URLSessionDidFinishEventsForBackgroundURLSession:消息档址,這就意味著之前這個session中已經(jīng)入隊的所有消息都轉發(fā)出去了守伸,這時候再調用先前存取的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 有的話轉發(fā)出去剂娄,通過這4個參數(shù)玄呛,返回一個NSURLRequest類型參數(shù)徘铝,request轉發(fā)惕它、網(wǎng)絡重定向.
    if (self.taskWillPerformHTTPRedirection) {
        //用自己自定義的一個重定向的block實現(xiàn)淹魄,返回一個新的request。
        redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
    }

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

一開始我以為這個方法是類似NSURLProtocol扳炬,可以在請求時自己主動的去重定向request,后來發(fā)現(xiàn)不是疚俱,這個方法是在服務器去重定向的時候呆奕,才會被調用衬吆。為此我寫了段簡單的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 的網(wǎng)絡堆棧(networking stack)是一樣的横腿,具有相同的共享 NSHTTPCookieStorage耿焊,共享 NSURLCache 和共享NSURLCredentialStorage罗侯。
+ephemeralSessionConfiguration 返回一個預設配置钩杰,這個配置中不會對緩存,Cookie 和證書進行持久性的存儲措左。這對于實現(xiàn)像秘密瀏覽這種功能來說是很理想的怎披。
+backgroundSessionConfiguration:(NSString *)identifier 的獨特之處在于钳枕,它會創(chuàng)建一個后臺 session鱼炒。后臺 session 不同于常規(guī)的蝌借,普通的 session,它甚至可以在應用程序掛起凝化,退出或者崩潰的情況下運行上傳和下載任務搓劫。初始化時指定的標識符枪向,被用于向任何可能在進程外恢復后臺傳輸?shù)氖刈o進程(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認證滔迈,功能一樣,執(zhí)行的內容也完全一樣盼理。
區(qū)別在于這個是non-session-level級別的認證俄删,而之前的是session-level級別的畴椰。
相對于它斜脂,多了一個參數(shù)task,然后調用我們自定義的Block會多回傳這個task作為參數(shù)帚戳,這樣我們就可以根據(jù)每個task去自定義我們需要的https認證方式。

代理6:

//當一個session task需要發(fā)送一個新的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);
    }
}

該代理方法會在下面兩種情況被調用:
如果task是由uploadTaskWithStreamedRequest:創(chuàng)建的炬灭,那么提供初始的request body stream時候會調用該代理方法靡菇。
因為認證挑戰(zhàn)或者其他可恢復的服務器錯誤厦凤,而導致需要客戶端重新發(fā)送一個含有body stream的request较鼓,這時候會調用該代理博烂。

代理7:

/*
 //周期性地通知代理發(fā)送到服務器端數(shù)據(jù)的進度禽篱。
 */

- (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ù)給服務器悼吱,會回調這個方法后添,通知已經(jīng)發(fā)送了多少,總共要發(fā)送多少猎醇。
代理方法里也就是僅僅調用了我們自定義的Block而已。

未完總結: 寫的太深奧了阻问,我是參考別人的文章一路看過來的称近。有些東西還是沒有理解刨秆,但是大體的意思懂了衡未,也學習了不少思想缓醋。研究了好幾天送粱。先到這吧抗俄。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末动雹,一起剝皮案震驚了整個濱河市洽胶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌喷好,老刑警劉巖梗搅,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件无切,死亡現(xiàn)場離奇詭異哆键,居然都是意外死亡籍嘹,警方通過查閱死者的電腦和手機辱士,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門颂碘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來头岔,“玉大人切油,你說我怎么就攤上這事澎胡」ニ” “怎么了戚宦?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵受楼,是天一觀的道長艳汽。 經(jīng)常有香客問我河狐,道長,這世上最難降的妖魔是什么迈套? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任桑李,我火速辦了婚禮芙扎,結果婚禮上戒洼,老公的妹妹穿的比我還像新娘圈浇。我一直安慰自己磷蜀,他們只是感情好褐隆,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布庶弃。 她就那樣靜靜地躺著歇攻,像睡著了一般缴守。 火紅的嫁衣襯著肌膚如雪屡穗。 梳的紋絲不亂的頭發(fā)上鸡捐,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音色迂,去河邊找鬼歇僧。 笑死锋拖,一個胖子當著我的面吹牛兽埃,可吹牛的內容都是我干的舷夺。 我是一名探鬼主播给猾,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼敢伸,長吁一口氣:“原來是場噩夢啊……” “哼池颈!你這毒婦竟也來了饶辙?” 一聲冷哼從身側響起弃揽,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涌矢,沒想到半個月后娜庇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體名秀,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡匕得,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年略吨,在試婚紗的時候發(fā)現(xiàn)自己被綠了翠忠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片负间。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡政溃,死狀恐怖董虱,靈堂內的尸體忽然破棺而出愤诱,到底是詐尸還是另有隱情淫半,我是刑警寧澤科吭,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站猴鲫,受9級特大地震影響对人,放射性物質發(fā)生泄漏。R本人自食惡果不足惜拂共,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一牺弄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧势告,春花似錦蛇捌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吵护,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間表鳍,已是汗流浹背馅而。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留譬圣,地道東北人瓮恭。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像厘熟,于是被迫代替她去往敵國和親屯蹦。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容