其核心固然是網絡通訊模塊AFURLSessionManager。你們都知道,AF3.x是基於NSURLSession來封裝的。因此這個類圍繞着NSURLSession作了一系列的封裝。而其他的四個模塊,均是爲了配合網絡通訊或對已有UIKit的一個擴展工具包。php
這五個模塊所對應的類的結構關係圖以下所示:ios
其中AFHTTPSessionManager是繼承於AFURLSessionManager的,咱們通常作網絡請求都是用這個類, 可是它自己是沒有作實事的,只是作了一些簡單的封裝,把請求邏輯分發給父類AFURLSessionManager或者其它類去作。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存了起來,還生成了一個請求序列對象和一個響應序列對象。後面再細說這兩個類是幹什麼用的。編程
- (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中找到告終論:github 。bash
初始化方法到這就所有完成了。
小編這呢,給你們推薦一個優秀的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
複製代碼
這裏是用來忽略:?帶來的警告,具體的各類編譯器警告描述,能夠參考這篇:各類編譯器的警告。
- (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;
}
複製代碼
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)。
- (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;
}
複製代碼
繞了一圈咱們又回來了。。
言歸正傳,咱們接着調用了父類的生成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;
}
複製代碼
@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];
}
複製代碼
[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];
}
複製代碼
- (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);
}
}
}
複製代碼
downloadProgressBlock
和uploadProgressBlock
。主要的做用就是爲了讓進度實時的傳遞。到這裏咱們整個對task的處理就完成了。
小編這呢,給你們推薦一個優秀的iOS交流平臺,平臺裏的夥伴們都是很是優秀的iOS開發人員,咱們專一於技術的分享與技巧的交流,你們能夠在平臺上討論技術,交流學習。歡迎你們的加入(想要加入的可加小編微信15673450590)。
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>
複製代碼
- (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;
}
複製代碼
方法都是同樣的,就不重複粘貼佔篇幅了。
主要談談這個設計思路
- (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];
}
複製代碼
代理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迴應服務器端的挑戰。
這裏,我把官方文檔對這個方法的描述翻譯了一下。
總結一下,這個方法其實就是作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);
});
}
}
複製代碼
官方文檔翻譯:
函數討論:
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);
}
}
複製代碼
<?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);
}
}
複製代碼
代理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);
}
}
複製代碼
代理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);
}
}
複製代碼
轉載於做者:塗耀輝
連接:www.jianshu.com/p/856f0e262…