AFNetworking基本每個項目都會用。但是看它的代碼的人不多。有一次面試冀宴,面試官問我看過AFNetworking的源碼沒?我說沒有嚎杨,他說了句不看源碼怎么提高花鹅?然后接著一句你們年輕人太浮躁了氧腰。雖然他說的我當時心里挺不爽枫浙,但是人家說的很有道理」潘苦口良藥箩帚。項目不忙的時候,我會看會兒黄痪,把看到的和學到的總結下紧帕。方便對網(wǎng)絡有個更深的理解。AFNetworking下載地址https://github.com/AFNetworking/AFNetworking/tree/3.1.0
結構
可以看到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的一個擴展工具包遭铺。
這五個模塊所對應的類的結構關系圖如下所示:
其中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)絡的時候,這些代理開始調用了:
AFUrlSessionManager一共實現(xiàn)了如上圖所示這么一大堆NSUrlSession相關的代理穿稳。(小伙伴們的順序可能不一樣存皂,樓主根據(jù)代理隸屬重新排序了一下)
而只轉發(fā)了其中3條到AF自定義的delegate中:
這就是我們一開始說的,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而已。