原創(chuàng):知識(shí)點(diǎn)總結(jié)性文章
創(chuàng)作不易,請(qǐng)珍惜,之后會(huì)持續(xù)更新,不斷完善
個(gè)人比較喜歡做筆記和寫總結(jié),畢竟好記性不如爛筆頭哈哈,這些文章記錄了我的IOS成長(zhǎng)歷程,希望能與大家一起進(jìn)步
溫馨提示:由于簡(jiǎn)書不支持目錄跳轉(zhuǎn),大家可通過command + F 輸入目錄標(biāo)題后迅速尋找到你所需要的內(nèi)容
目錄
- 一、功能模塊
- 二、初始化方法
- 1、層層調(diào)用
- 2、最終調(diào)用的初始化方法
- 3、父類 AFURLSessionManager 的初始化方法
- 三、GET請(qǐng)求方法
- 1、生成 dataTask
- 2、生成request
- 3、觀察 NSURLRequest 的屬性
- 4、請(qǐng)求序列化
- 四、NSURLSessionDelegate的實(shí)現(xiàn)
- 1、代理之間的繼承關(guān)系
- 2、NSURLSessionDelegate
- 3、NSURLSessionTaskDelegate
- 4、NSURLSessionDownloadDelegate
- Demo
- 參考文獻(xiàn)
作為一個(gè)iOS開發(fā)者,也許你不知道NSURLRequest、不知道NSURLSession...(說不下去了...怎么會(huì)什么都不知道...)但是你一定知道AFNetworking。大多數(shù)人習(xí)慣了只要是請(qǐng)求網(wǎng)絡(luò)都用AF,但是你真的知道AF做了什么嗎?為什么我們不用原生的NSURLSession而選擇AFNetworking? 本文將從源碼的角度去分析AF的實(shí)際作用。
一、功能模塊
- 網(wǎng)絡(luò)通信模塊(
AFURLSessionManager、AFHTTPSessionManger) - 網(wǎng)絡(luò)狀態(tài)監(jiān)聽模塊(
Reachability) - 網(wǎng)絡(luò)通信安全策略模塊(
Security) - 網(wǎng)絡(luò)通信信息序列化/反序列化模塊(
Serialization) - 對(duì)于
iOS UIKit庫(kù)的擴(kuò)展(UIKit)

核心當(dāng)然是網(wǎng)絡(luò)通信模塊AFURLSessionManager。AF3.x是基于NSURLSession來封裝的。所以這個(gè)類圍繞著NSURLSession做了一系列的封裝。而其余的四個(gè)模塊,均是為了配合網(wǎng)絡(luò)通信或?qū)σ延?code>UIKit的一個(gè)擴(kuò)展工具包。
其中AFHTTPSessionManager是繼承于AFURLSessionManager的,我們一般做網(wǎng)絡(luò)請(qǐng)求都是用這個(gè)類,但是它本身是沒有做實(shí)事的,只是做了一些簡(jiǎn)單的封裝,把請(qǐng)求邏輯分發(fā)給父類AFURLSessionManager或者其它類去做。
這五個(gè)模塊所對(duì)應(yīng)的類的結(jié)構(gòu)關(guān)系圖如下所示:

二、初始化方法
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
1、層層調(diào)用
可以看到,調(diào)用了初始化方法生成了一個(gè)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];
}
2、最終調(diào)用的初始化方法
初始化方法調(diào)用父類的初始化方法,其父類也就是AF3.x最最核心的類AFURLSessionManager。除此之外,方法中把baseURL存了起來,還生成了一個(gè)請(qǐng)求序列對(duì)象和一個(gè)響應(yīng)序列對(duì)象。
- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
...
return self;
}
? 調(diào)用父類初始化方法
self = [super initWithSessionConfiguration:configuration];
if (!self)
{
return nil;
}
? url有值且最后不包含/,那么在url的baseurlpath末尾添加/
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"])
{
url = [url URLByAppendingPathComponent:@"/"];
}
self.baseURL = url;
? 給requestSerializer、responseSerializer設(shè)置默認(rèn)值
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
3、父類 AFURLSessionManager 的初始化方法
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration
{
self = [super init];
if (!self)
{
return nil;
}
.....
return self;
}
? 設(shè)置默認(rèn)的configuration,配置我們的session
if (!configuration)
{
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
// 持有configuration
self.sessionConfiguration = configuration;
? 設(shè)置delegate的操作隊(duì)列并發(fā)的線程數(shù)量1,也就是串行隊(duì)列
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
? 如果完成后需要做復(fù)雜(耗時(shí))的處理,可以選擇異步隊(duì)列。如果完成后直接更新UI,可以選擇主隊(duì)列 [NSOperationQueue mainQueue]
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
? 默認(rèn)為json解析
self.responseSerializer = [AFJSONResponseSerializer serializer];
? 設(shè)置默認(rèn)證書為無條件信任證書進(jìn)行https認(rèn)證
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
? 網(wǎng)絡(luò)狀態(tài)監(jiān)聽
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
? 設(shè)置存儲(chǔ)詞典。每一個(gè)task都會(huì)被匹配一個(gè)AFURLSessionManagerTaskDelegate來做task的delegate,進(jìn)行事件處理
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
? 使用NSLock確保詞典在多線程訪問時(shí)的線程安全
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
? 置空task關(guān)聯(lián)的代理
異步獲取當(dāng)前session的所有未完成的task。其實(shí)講道理來說在初始化中調(diào)用這個(gè)方法里面應(yīng)該一個(gè)task都不會(huì)有,打斷點(diǎn)去看,也確實(shí)如此,里面的數(shù)組都是空的。但當(dāng)后臺(tái)任務(wù)重新回來初始化session,可能就會(huì)有先前的請(qǐng)求任務(wù),導(dǎo)致程序的crash。
[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];
}
}];
二、GET請(qǐng)求方法
[manager GET:@"http://localhost" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
...
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
...
}];
在AFHTTPSessionManager類中Get請(qǐng)求方法會(huì)調(diào)用父類,也就是我們整個(gè)AF3.x的核心類AFURLSessionManager的Get請(qǐng)求方法,生成了一個(gè)系統(tǒng)的NSURLSessionDataTask實(shí)例,并且開始網(wǎng)絡(luò)請(qǐng)求。
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
...
return dataTask;
}
1、生成 dataTask
a、在Get方法中生成一個(gè)dataTask,然后開始網(wǎng)絡(luò)請(qǐng)求
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
b、生成 dataTask 的方法
- (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
{
......
return dataTask;
}
? 先調(diào)用AFHTTPRequestSerializer的requestWithMethod函數(shù)構(gòu)建request。relativeToURL表示將URLString拼接到baseURL后面
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
? 處理request構(gòu)建產(chǎn)生的錯(cuò)誤。如果解析錯(cuò)誤,直接返回
NSError *serializationError = nil;
if (serializationError)
{
if (failure)
{
...
}
return nil;
}
? diagnostic是診斷的意思,常與push pop搭配,來忽略一些編譯器的警告,這里是用來忽略 ?帶來的警告。completionQueue是我們自定義的一個(gè)GCD的Queue,如果設(shè)置了那么從這個(gè)Queue中回調(diào)結(jié)果,不存在則從主隊(duì)列回調(diào)。
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
? 此時(shí)的request已經(jīng)將參數(shù)拼接在url后面,根據(jù)request來生成dataTask
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
......
}];
? 在完成的回調(diào)里,調(diào)用我們傳過來的成功和失敗的回調(diào)
if (error)
{
if (failure)
{
failure(dataTask, error);
}
}
else
{
if (success)
{
success(dataTask, responseObject);
}
}
2、生成request
使用指定的HTTP method和URLString來構(gòu)建一個(gè)NSMutableURLRequest對(duì)象實(shí)例。如果method是GET、HEAD、DELETE,那parameter將會(huì)被用來構(gòu)建一個(gè)基于url編碼的查詢字符串(query url),并且這個(gè)字符串會(huì)直接加到request的url后面。對(duì)于其他的Method,比如POST/PUT,它們會(huì)根據(jù)parameterEncoding屬性進(jìn)行編碼,而后加到request的http body上。
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
......
return mutableRequest;
}
? 斷言:debug模式下,如果該參數(shù)為nil,crash并直接打印出來
NSParameterAssert(method);
NSParameterAssert(URLString);
? 我們傳進(jìn)來的是一個(gè)字符串,在這里它幫你轉(zhuǎn)成url
NSURL *url = [NSURL URLWithString:URLString];
? 設(shè)置請(qǐng)求方式(get、post、put.....)
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
? 將request的各種屬性遍歷。將觀察到發(fā)生變化的屬性添加到NSMutableURLRequest(如:timeout)。通過KVC動(dòng)態(tài)的給mutableRequest添加value
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths())
{
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath])
{
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
? 將傳入的parameters進(jìn)行編碼,拼接到url后并返回
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
3、觀察 NSURLRequest 的屬性
a、觀察屬性變化
這個(gè)c函數(shù)封裝了一些NSURLRequest屬性相關(guān)的方法名并將其作為數(shù)組返回
static NSArray * AFHTTPRequestSerializerObservedKeyPaths()
{
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths =
@[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}
allowsCellularAccess
是否允許使用設(shè)備的蜂窩移動(dòng)網(wǎng)絡(luò)來創(chuàng)建request,默認(rèn)為允許。
cachePolicy
創(chuàng)建的request所使用的緩存策略,默認(rèn)使用NSURLRequestUseProtocolCachePolicy,該策略表示如果緩存不存在,直接從服務(wù)端獲取。如果緩存存在,會(huì)根據(jù)response中的Cache-Control字段判斷下一步操作,如: Cache-Control字段為must-revalidata,則詢問服務(wù)端該數(shù)據(jù)是否有更新,無更新的話直接返回給用戶緩存數(shù)據(jù),若已更新,則請(qǐng)求服務(wù)端。
HTTPShouldHandleCookies
如果設(shè)置HTTPShouldHandleCookies為YES,就處理存儲(chǔ)在NSHTTPCookieStore中的cookies,HTTPShouldHandleCookies表示是否應(yīng)該給request設(shè)置cookie并隨request一起發(fā)送出去。
HTTPShouldUsePipelining
表示receiver(理解為iOS客戶端)的下一個(gè)信息是否必須等到上一個(gè)請(qǐng)求回復(fù)才能發(fā)送。如果為YES表示必須等receiver收到先前的回復(fù)才能發(fā)送下個(gè)信息。
networkServiceType
設(shè)定request的網(wǎng)絡(luò)服務(wù)類型,默認(rèn)是NSURLNetworkServiceTypeDefault。這個(gè)網(wǎng)絡(luò)服務(wù)是為了告訴系統(tǒng)網(wǎng)絡(luò)層這個(gè)request使用的目的,比如NSURLNetworkServiceTypeVoIP表示這個(gè)request是用來請(qǐng)求網(wǎng)際協(xié)議通話技術(shù)(Voice over IP)。系統(tǒng)能根據(jù)提供的信息來優(yōu)化網(wǎng)絡(luò)處理,從而優(yōu)化電池壽命,網(wǎng)絡(luò)性能等等,客戶端基本不使用。
timeoutInterval:超時(shí)機(jī)制,默認(rèn)60秒
b、某個(gè)request需要觀察的屬性集合
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
在-init方法里對(duì)這個(gè)集合進(jìn)行了初始化,并且對(duì)當(dāng)前類的和NSURLRequest相關(guān)的那些屬性添加了KVO監(jiān)聽。
- (instancetype)init
{
.......
// 每次都會(huì)重置變化
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths())
{
if ([self respondsToSelector:NSSelectorFromString(keyPath)])
{
// 給request各種屬性的set方法添加觀察者
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
.......
}
c、對(duì)應(yīng)的KVO觸發(fā)的方法
如果KVO的觸發(fā)機(jī)制是默認(rèn)觸發(fā),則返回true,否則返回false。在這里,只要是AFHTTPRequestSerializerObservedKeyPaths里面的屬性,我們都取消自動(dòng)觸發(fā)kvo機(jī)制,使用手動(dòng)觸發(fā)。為什么手動(dòng),我猜應(yīng)該是為了在監(jiān)聽這些屬性時(shí)可以用于某些特殊的操作,比如測(cè)試這些屬性變化是否崩潰等。
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key])
{
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
當(dāng)觀察到這些set方法被調(diào)用了,而且不為Null就會(huì)添加到集合里,否則移除。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == AFHTTPRequestSerializerObserverContext)
{
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]])
{
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
}
else
{
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
4、請(qǐng)求序列化
舉個(gè)例子演示下整個(gè)轉(zhuǎn)碼過程。
@{
@"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ù)變成字符串類型。
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSMutableURLRequest *mutableRequest = [request mutableCopy];
...
return mutableRequest;
}
a、設(shè)置請(qǐng)求方法
- 請(qǐng)求行(狀態(tài)行):get、url、http協(xié)議
- 請(qǐng)求頭:content-type、accept-language
- 請(qǐng)求體:get參數(shù)拼接在url后面,post數(shù)據(jù)放在body
? 從self.HTTPRequestHeaders里去遍歷拿到設(shè)置的參數(shù),如果request此項(xiàng)無值則給mutableRequest.headfiled賦值
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field])
{
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
? 將我們傳入的字典轉(zhuǎn)成字符串。至于轉(zhuǎn)碼方式,我們可以使用自定義的方式,也可以用AF默認(rèn)的轉(zhuǎn)碼方式
NSString *query = nil;
if (parameters)
{
// 自定義的解析方式
if (self.queryStringSerialization)
{
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
}
// 默認(rèn)解析方式 count=5&start=1
else
{
switch (self.queryStringSerializationStyle)
{
case AFHTTPRequestQueryStringDefaultStyle:
// 將parameters傳入這個(gè)c函數(shù)
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
? 最后判斷該request中是否包含了GET、HEAD、DELETE,因?yàn)檫@幾個(gè)method的query是拼接到url后面的,而POST、PUT是把query拼接到http body中的。application/x-www-form-urlencoded是常用的表單發(fā)包方式,普通的表單提交,或者js發(fā)包,默認(rèn)都是通過這種方式。
// get等請(qǐng)求
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]])
{
if (query && query.length > 0)
{
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
}
// post等請(qǐng)求
else
{
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"])
{
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
// 設(shè)置請(qǐng)求體
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
b、默認(rèn)的解析方式
NSString * AFQueryStringFromParameters(NSDictionary *parameters)
{
// 一對(duì)
NSMutableArray *mutablePairs = [NSMutableArray array];
// 把參數(shù)傳給AFQueryStringPairsFromDictionary
// AFQueryStringPair是數(shù)據(jù)處理類
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters))
{
// 百分號(hào)編碼
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
// 將拆分?jǐn)?shù)組進(jìn)行拼接返回參數(shù)字符串
return [mutablePairs componentsJoinedByString:@"&"];
}
c、數(shù)據(jù)處理類
AFQueryStringPair是一個(gè)數(shù)據(jù)處理類,只有兩個(gè)屬性:field和value。方法:URLEncodedStringValue的作用是以key=value的形式,用百分號(hào)編碼。
@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (NSString *)URLEncodedStringValue;
@end
- (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])];
}
}
d、請(qǐng)求參數(shù)對(duì)
? AFQueryStringPairsFromDictionary只是起過渡作用,往下繼續(xù)調(diào)用。
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary)
{
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
? 在AFQueryStringPairsFromKeyAndValue中使用了遞歸,會(huì)對(duì)value的類型進(jìn)行判斷,有NSDictionary、NSArray、NSSet類型。
不過有人就會(huì)問了,在AFQueryStringPairsFromDictionary中給AFQueryStringPairsFromKeyAndValue函數(shù)傳入的value不是NSDictionary嘛?還要判斷那么多類型干啥?對(duì),問得很好,這就是AFQueryStringPairsFromKeyAndValue的核心——遞歸調(diào)用并解析,你不能保證NSDictionary的value中存放的不是一個(gè)NSArray、NSSet。
NSArray *AFQueryStringPairsFromKeyAndValue(NSString *key, id value)
{
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
...
return mutableQueryStringComponents;
}
? 根據(jù)需要排列的對(duì)象的description來進(jìn)行升序排列。因?yàn)閷?duì)象的description返回的是NSString,所以此處compare:使用的是NSString的compare函數(shù)。 比如:@[@"foo", @"bar", @"bae"] ----> @[@"bae", @"bar",@"foo"]
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
? 判斷vaLue是什么類型的,然后去遞歸調(diào)用自己,直到解析的是除了array、dictionary、set以外的元素,然后把得到的參數(shù)數(shù)組返回
if ([value isKindOfClass:[NSDictionary class]])
{
NSDictionary *dictionary = value;
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)];
}
}
既然是遞歸,那么就要有結(jié)束遞歸的情況,比如解析到最后,對(duì)應(yīng)value是一個(gè)NSString,那么就得調(diào)用函數(shù)中最后的else語(yǔ)句。
else
{
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
四、NSURLSession相關(guān)的代理
1、代理之間的繼承關(guān)系
在第一步探索父類AFURLSessionManager的初始化方法時(shí),有句代碼把AFURLSessionManager作為了所有的task的delegate。
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration
{
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
.......
}
這樣當(dāng)我們請(qǐng)求網(wǎng)絡(luò)的時(shí)候,AFUrlSessionManager實(shí)現(xiàn)的這一大堆NSURLSession相關(guān)的代理開始調(diào)用了。

其中有3條重要的,它們轉(zhuǎn)發(fā)到了AFURLSessionManagerTaskDelegate即AF自定義的代理,負(fù)責(zé)把每個(gè)task對(duì)應(yīng)的數(shù)據(jù)回調(diào)出去。

@interface AFURLSessionManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying>
@protocol NSURLSessionDelegate <NSObject>
@protocol NSURLSessionTaskDelegate <NSURLSessionDelegate>
@protocol NSURLSessionDataDelegate <NSURLSessionTaskDelegate>
@protocol NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
@protocol NSURLSessionStreamDelegate <NSURLSessionTaskDelegate>
可以看到這些代理都是繼承關(guān)系,而在NSURLSession實(shí)現(xiàn)中,只要設(shè)置了這個(gè)代理,它會(huì)去判斷這些所有的代理是否respondsToSelector這些代理中的方法,如果響應(yīng)了就會(huì)去調(diào)用。每個(gè)代理方法對(duì)應(yīng)一個(gè)我們自定義的Block,如果Block被賦值了,那么就調(diào)用它。
2、NSURLSessionDelegate
a、didBecomeInvalidWithError:當(dāng)前session失效時(shí)會(huì)調(diào)用
如果你使用finishTasksAndInvalidate函數(shù)使該session失效,那么session首先會(huì)先完成最后一個(gè)task,然后再調(diào)用URLSession:didBecomeInvalidWithError:代理方法。如果你調(diào)用invalidateAndCancel方法來使session失效,那么該session會(huì)立即調(diào)用這個(gè)代理方法。
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
if (self.sessionDidBecomeInvalid)
{
self.sessionDidBecomeInvalid(session, error);
}
// 不過源代碼中沒有舉例如何使用這個(gè)Notification,所以需要用戶自己定義,比如結(jié)束進(jìn)度條的顯示
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}
b、didReceiveChallenge:收到服務(wù)端的challenge,例如https需要驗(yàn)證證書等
web服務(wù)器接收到客戶端請(qǐng)求時(shí),有時(shí)候需要先驗(yàn)證客戶端是否為正常用戶,再?zèng)Q定是夠返回真實(shí)數(shù)據(jù)。這種情況稱之為服務(wù)端要求客戶端接收挑戰(zhàn)。接收到挑戰(zhàn)后,客戶端要根據(jù)服務(wù)端傳來的challenge來生成completionHandler所需的NSURLSessionAuthChallengeDisposition和NSURLCredential。disposition指定應(yīng)對(duì)這個(gè)挑戰(zhàn)的方法,而credential是客戶端生成的挑戰(zhàn)證書,注意只有challenge中認(rèn)證方法為NSURLAuthenticationMethodServerTrust的時(shí)候,才需要生成挑戰(zhàn)證書。最后調(diào)用completionHandler回應(yīng)服務(wù)器端的挑戰(zhàn)。
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
...
}
? 設(shè)置挑戰(zhàn)處理類型為默認(rèn)
- NSURLSessionAuthChallengeUseCredential:使用指定的證書
- NSURLSessionAuthChallengePerformDefaultHandling:默認(rèn)方式處理
- NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰(zhàn)
- NSURLSessionAuthChallengeRejectProtectionSpace:拒絕此挑戰(zhàn),并嘗試下一個(gè)驗(yàn)證保護(hù)空間
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
? 自定義方法,用來應(yīng)對(duì)服務(wù)器端的認(rèn)證挑戰(zhàn)
__block NSURLCredential *credential = nil;// 證書
if (self.sessionDidReceiveAuthenticationChallenge)
{
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
}
? 倘若沒有自定義Block
// 判斷接收服務(wù)器挑戰(zhàn)的方法是否是信任證書
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
// 只需要驗(yàn)證服務(wù)端證書是否安全,即https的單向認(rèn)證,這是AF默認(rèn)處理的認(rèn)證方式
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host])
{
// 信任評(píng)估通過,就從受保護(hù)空間里面拿出證書,回調(diào)給服務(wù)器,告訴服務(wù)器,我信任你,你給我發(fā)送數(shù)據(jù)吧
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 確定挑戰(zhàn)的方式
if (credential)
{
disposition = NSURLSessionAuthChallengeUseCredential;// 證書挑戰(zhàn)
}
else
{
disposition = NSURLSessionAuthChallengePerformDefaultHandling;// 默認(rèn)挑戰(zhàn)
}
}
else
{
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;// 取消挑戰(zhàn)
}
}
// 默認(rèn)挑戰(zhàn)方式
else
{
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
? 完成挑戰(zhàn),將信任憑證發(fā)送給服務(wù)端
if (completionHandler)
{
completionHandler(disposition, credential);
}
4、NSURLSessionDownloadDelegate
a、didFinishDownloadingToURL
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
// 這個(gè)是session也就是全局的,后面的個(gè)人代理也會(huì)做同樣的這件事
if (self.downloadTaskDidFinishDownloading)
{
// 自定義函數(shù),根據(jù)從服務(wù)器端獲取到的數(shù)據(jù)臨時(shí)地址location等參數(shù)構(gòu)建出你想要將臨時(shí)文件移動(dòng)的位置
NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (fileURL)
{
// 如果fileURL存在的話,表示用戶希望把臨時(shí)數(shù)據(jù)存起來
delegate.downloadFileURL = fileURL;
NSError *error = nil;
// 將位于location位置的文件全部移到fileURL位置
[[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
// 如果移動(dòng)文件失敗,就發(fā)送AFURLSessionDownloadTaskDidFailToMoveFileNotification
if (error)
{
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
}
return;
}
}
// 轉(zhuǎn)發(fā)代理:這一步比較詭異,感覺有重復(fù)的嫌疑
if (delegate)
{
[delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
}
}
b、didFinishDownloadingToURL:周期性地通知下載進(jìn)度調(diào)用
- bytesWritten:表示自上次調(diào)用該方法后,接收到的數(shù)據(jù)字節(jié)數(shù)
- totalBytesWritten:表示目前已經(jīng)接收到的數(shù)據(jù)字節(jié)數(shù)
- totalBytesExpectedToWrite:表示期望收到的文件總字節(jié)數(shù)
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
if (self.downloadTaskDidWriteData)
{
self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
}
c、didResumeAtOffset:下載任務(wù)重新開始下載
如果一個(gè)resumable下載任務(wù)被取消或者失敗了,你可以請(qǐng)求一個(gè)resumeData對(duì)象(比如在userInfo字典中通過NSURLSessionDownloadTaskResumeData這個(gè)鍵來獲取到resumeData),并使用它來提供足夠的信息以重新開始下載任務(wù)。
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
if (self.downloadTaskDidResume)
{
self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
}
}
Demo
Demo在我的Github上,歡迎下載。
SourceCodeAnalysisDemo