AFNetWorking
AFNetWorking一款輕量級(jí)網(wǎng)絡(luò)請(qǐng)求開(kāi)源框架眯漩,基于iOS和mac os 網(wǎng)絡(luò)進(jìn)行擴(kuò)展的高性能框架赫模,大大降低了iOS開(kāi)發(fā)工程師處理網(wǎng)絡(luò)請(qǐng)求的難度慎宾,讓iOS開(kāi)發(fā)變成一件愉快的事情。
GitHub地址:https://github.com/AFNetworking/AFNetworking
AFN優(yōu)點(diǎn):
1.原有基礎(chǔ)urlsesson上封裝了一層甸赃,在傳參方面更靈活柿汛,
2.回調(diào)更友好,
3.支持返回?cái)?shù)據(jù)序列化
4.支持文件上傳埠对,斷點(diǎn)下載络断,
5.自帶多線程,防死鎖
6.處理了Https證書流程项玛,節(jié)省移動(dòng)端開(kāi)發(fā)
7.支持網(wǎng)絡(luò)狀態(tài)判斷
先從最新的AF3.x講起吧:
- 首先貌笨,我們就一起分析一下該框架的組成。
將AF下載導(dǎo)入工程后襟沮,下面是其包結(jié)構(gòu)躁绸,相對(duì)于2.x變得非常簡(jiǎn)單了:
除去Support Files,可以看到AF分為如下5個(gè)功能模塊:
- 網(wǎng)絡(luò)通信模塊(AFURLSessionManager臣嚣、AFHTTPSessionManger)
- 網(wǎng)絡(luò)狀態(tài)監(jiān)聽(tīng)模塊(Reachability)
- 網(wǎng)絡(luò)通信安全策略模塊(Security)
- 網(wǎng)絡(luò)通信信息序列化/反序列化模塊(Serialization)
-
對(duì)于iOS UIKit庫(kù)的擴(kuò)展(UIKit)
其核心當(dāng)然是網(wǎng)絡(luò)通信模塊AFURLSessionManager净刮。大家都知道,AF3.x是基于NSURLSession來(lái)封裝的硅则。所以這個(gè)類圍繞著NSURLSession做了一系列的封裝淹父。而其余的四個(gè)模塊,均是為了配合網(wǎng)絡(luò)通信或?qū)σ延蠻IKit的一個(gè)擴(kuò)展工具包怎虫。
這五個(gè)模塊所對(duì)應(yīng)的類的結(jié)構(gòu)關(guān)系圖如下所示:
其中AFHTTPSessionManager是繼承于AFURLSessionManager的暑认,我們一般做網(wǎng)絡(luò)請(qǐng)求都是用這個(gè)類,但是它本身是沒(méi)有做實(shí)事的大审,只是做了一些簡(jiǎn)單的封裝蘸际,把請(qǐng)求邏輯分發(fā)給父類AFURLSessionManager或者其它類去做。
首先我們簡(jiǎn)單的寫個(gè)get請(qǐng)求:
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) {
}];
首先我們我們調(diào)用了初始化方法生成了一個(gè)manager徒扶,我們點(diǎn)進(jìn)去看看初始化做了什么:
- (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;
}
//對(duì)傳過(guò)來(lái)的BaseUrl進(jìn)行處理粮彤,如果有值且最后不包含/,url加上"/"
//--經(jīng)一位熱心讀者更正...以后注釋也一定要走心啊...不能誤導(dǎo)大家...
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
self.baseURL = url;
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
- 初始化都調(diào)用到
- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration
方法中來(lái)了。 - 其實(shí)初始化方法都調(diào)用父類的初始化方法导坟。父類也就是AF3.x最最核心的類AFURLSessionManager屿良。幾乎所有的類都是圍繞著這個(gè)類在處理業(yè)務(wù)邏輯。
- 除此之外惫周,方法中把baseURL存了起來(lái)尘惧,還生成了一個(gè)請(qǐng)求序列對(duì)象和一個(gè)響應(yīng)序列對(duì)象。后面再細(xì)說(shuō)這兩個(gè)類是干什么用的递递。
直接來(lái)到父類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ù)設(shè)置為1
self.operationQueue.maxConcurrentOperationCount = 1;
//注意代理喷橙,代理的繼承,實(shí)際上NSURLSession去判斷了登舞,你實(shí)現(xiàn)了哪個(gè)方法會(huì)去調(diào)用重慢,包括子代理的方法!
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
//各種響應(yīng)轉(zhuǎn)碼
self.responseSerializer = [AFJSONResponseSerializer serializer];
//設(shè)置默認(rèn)安全策略
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
// 設(shè)置存儲(chǔ)NSURL task與AFURLSessionManagerTaskDelegate的詞典(重點(diǎn)逊躁,在AFNet中似踱,每一個(gè)task都會(huì)被匹配一個(gè)AFURLSessionManagerTaskDelegate 來(lái)做task的delegate事件處理) ===============
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
// 設(shè)置AFURLSessionManagerTaskDelegate 詞典的鎖,確保詞典在多線程訪問(wèn)時(shí)的線程安全
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
// 置空task關(guān)聯(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;
}
- 這個(gè)就是最終的初始化方法了稽煤,注釋應(yīng)該寫的很清楚核芽,唯一需要說(shuō)的就是三點(diǎn):
-
self.operationQueue.maxConcurrentOperationCount = 1;
這個(gè)operationQueue就是我們代理回調(diào)的queue。這里把代理回調(diào)的線程并發(fā)數(shù)設(shè)置為1了酵熙。至于這里為什么要這么做轧简,我們先留一個(gè)坑,等我們講完AF2.x之后再來(lái)分析這一塊匾二。
-
- 第二就是我們初始化了一些屬性哮独,其中包括
self.mutableTaskDelegatesKeyedByTaskIdentifier
,這個(gè)是用來(lái)讓每一個(gè)請(qǐng)求task和我們自定義的AF代理來(lái)建立映射用的察藐,其實(shí)AF對(duì)task的代理進(jìn)行了一個(gè)封裝皮璧,并且轉(zhuǎn)發(fā)代理到AF自定義的代理,這是AF比較重要的一部分分飞,接下來(lái)我們會(huì)具體講這一塊悴务。 - 第三就是下面這個(gè)方法:
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
}];
首先說(shuō)說(shuō)這個(gè)方法是干什么用的:這個(gè)方法用來(lái)異步的獲取當(dāng)前session的所有未完成的task。其實(shí)講道理來(lái)說(shuō)在初始化中調(diào)用這個(gè)方法應(yīng)該里面一個(gè)task都不會(huì)有譬猫。我們打斷點(diǎn)去看讯檐,也確實(shí)如此,里面的數(shù)組都是空的染服。
但是想想也知道别洪,AF大神不會(huì)把一段沒(méi)用的代碼放在這吧。輾轉(zhuǎn)多處柳刮,終于從AF的issue中找到了結(jié)論:github 挖垛。
- 原來(lái)這是為了防止后臺(tái)回來(lái)痒钝,重新初始化這個(gè)session,一些之前的后臺(tái)請(qǐng)求任務(wù)晕换,導(dǎo)致程序的crash午乓。
初始化方法到這就全部完成了站宗。
接著我們來(lái)看看網(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
{
//生成一個(gè)task
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
//開(kāi)始網(wǎng)絡(luò)請(qǐng)求
[dataTask resume];
return dataTask;
}
方法走到類AFHTTPSessionManager中來(lái)闸准,調(diào)用父類,也就是我們整個(gè)AF3.x的核心類AFURLSessionManager的方法梢灭,生成了一個(gè)系統(tǒng)的NSURLSessionDataTask實(shí)例夷家,并且開(kāi)始網(wǎng)絡(luò)請(qǐng)求。
我們繼續(xù)往父類里看敏释,看看這個(gè)方法到底做了什么:
- (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;
//把參數(shù)库快,還有各種東西轉(zhuǎn)化為一個(gè)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"
//如果解析錯(cuò)誤,直接返回
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;
}
- 這個(gè)方法做了兩件事:
1.用self.requestSerializer和各種參數(shù)去獲取了一個(gè)我們最終請(qǐng)求網(wǎng)絡(luò)需要的NSMutableURLRequest實(shí)例钥顽。
2.調(diào)用另外一個(gè)方法dataTaskWithRequest去拿到我們最終需要的NSURLSessionDataTask實(shí)例义屏,并且在完成的回調(diào)里,調(diào)用我們傳過(guò)來(lái)的成功和失敗的回調(diào)蜂大。 - 注意下面這個(gè)方法闽铐,我們常用來(lái) push pop搭配,來(lái)忽略一些編譯器的警告:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop
這里是用來(lái)忽略:奶浦?帶來(lái)的警告兄墅,具體的各種編譯器警告描述,可以參考這篇:各種編譯器的警告澳叉。
- 說(shuō)到底這個(gè)方法還是沒(méi)有做實(shí)事隙咸,我們繼續(xù)到requestSerializer方法里去看,看看AF到底如何拼接成我們需要的request的:
接著我們跑到AFURLRequestSerialization類中:
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
//斷言成洗,debug模式下五督,如果缺少改參數(shù),crash
NSParameterAssert(method);
NSParameterAssert(URLString);
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
//將request的各種屬性循環(huán)遍歷
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
//如果自己觀察到的發(fā)生變化的屬性瓶殃,在這些方法里
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
//把給自己設(shè)置的屬性給request設(shè)置
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
//將傳入的parameters進(jìn)行編碼概荷,并添加到request中
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
- 講一下這個(gè)方法,這個(gè)方法做了3件事:
1)設(shè)置request的請(qǐng)求類型碌燕,get,post,put...等
2)往request里添加一些參數(shù)設(shè)置误证,其中AFHTTPRequestSerializerObservedKeyPaths()
是一個(gè)c函數(shù),返回一個(gè)數(shù)組修壕,我們來(lái)看看這個(gè)函數(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))];
});
//就是一個(gè)數(shù)組里裝了很多方法的名字,
return _AFHTTPRequestSerializerObservedKeyPaths;
}
其實(shí)這個(gè)函數(shù)就是封裝了一些屬性的名字,這些都是NSUrlRequest的屬性。
再來(lái)看看self.mutableObservedChangedKeyPaths
,這個(gè)是當(dāng)前類的一個(gè)屬性:
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
在-init方法對(duì)這個(gè)集合進(jìn)行了初始化譬巫,并且對(duì)當(dāng)前類的和NSUrlRequest相關(guān)的那些屬性添加了KVO監(jiān)聽(tīng):
//每次都會(huì)重置變化
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
{
//當(dāng)觀察到這些set方法被調(diào)用了芦昔,而且不為Null就會(huì)添加到集合里诱贿,否則移除
if (context == AFHTTPRequestSerializerObserverContext) {
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
至此我們知道self.mutableObservedChangedKeyPaths
其實(shí)就是我們自己設(shè)置的request屬性值的集合。
接下來(lái)調(diào)用:
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
用KVC的方式咕缎,把屬性值都設(shè)置到我們請(qǐng)求的request中去珠十。
3)把需要傳遞的參數(shù)進(jìn)行編碼,并且設(shè)置到request中去:
//將傳入的parameters進(jìn)行編碼凭豪,并添加到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里去遍歷焙蹭,如果有值則設(shè)置給request的head
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
//來(lái)把各種類型的參數(shù),array dic set轉(zhuǎn)化成字符串嫂伞,給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 {
//默認(rèn)解析方式
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
//最后判斷該request中是否包含了GET孔厉、HEAD、DELETE(都包含在HTTPMethodsEncodingParametersInURI)帖努。因?yàn)檫@幾個(gè)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請(qǐng)求
// #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"];
}
//設(shè)置請(qǐng)求體
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
這個(gè)方法做了3件事:
1.從self.HTTPRequestHeaders
中拿到設(shè)置的參數(shù)郑趁,賦值要請(qǐng)求的request里去
2.把請(qǐng)求網(wǎng)絡(luò)的參數(shù),從array dic set這些容器類型轉(zhuǎn)換為字符串姿搜,具體轉(zhuǎn)碼方式寡润,我們可以使用自定義的方式,也可以用AF默認(rèn)的轉(zhuǎn)碼方式舅柜。自定義的方式?jīng)]什么好說(shuō)的梭纹,想怎么去解析由你自己來(lái)決定。我們可以來(lái)看看默認(rèn)的方式:
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
//把參數(shù)給AFQueryStringPairsFromDictionary致份,拿到AF的一個(gè)類型的數(shù)據(jù)就一個(gè)key变抽,value對(duì)象,在URLEncodedStringValue拼接keyValue氮块,一個(gè)加到數(shù)組里
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
//拆分?jǐn)?shù)組返回參數(shù)字符串
return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
//往下調(diào)用
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
// 根據(jù)需要排列的對(duì)象的description來(lái)進(jìn)行升序排列绍载,并且selector使用的是compare:
// 因?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 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;
}
- 轉(zhuǎn)碼主要是以上三個(gè)函數(shù)阳谍,配合著注釋應(yīng)該也很好理解:主要是在遞歸調(diào)用
AFQueryStringPairsFromKeyAndValue
蛀柴。判斷vaLue是什么類型的,然后去遞歸調(diào)用自己矫夯,直到解析的是除了array dic set以外的元素鸽疾,然后把得到的參數(shù)數(shù)組返回。 - 其中有個(gè)
AFQueryStringPair
對(duì)象训貌,其只有兩個(gè)屬性和兩個(gè)方法:
@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])];
}
}
方法很簡(jiǎn)單制肮,現(xiàn)在我們也很容易理解這整個(gè)轉(zhuǎn)碼過(guò)程了,我們舉個(gè)例子梳理下旺订,就是以下這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
至此弄企,我們?cè)瓉?lái)的容器類型的參數(shù)超燃,就這樣變成字符串類型了区拳。
緊接著這個(gè)方法還根據(jù)該request中請(qǐng)求類型,來(lái)判斷參數(shù)字符串應(yīng)該如何設(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請(qǐng)求
// #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"];
}
//設(shè)置請(qǐng)求體
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
至此士葫,我們生成了一個(gè)request乞而。
我們?cè)倩氐紸FHTTPSessionManager類中來(lái),回到這個(gè)方法:
- (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;
//把參數(shù),還有各種東西轉(zhuǎn)化為一個(gè)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"
//如果解析錯(cuò)誤慢显,直接返回
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;
}
繞了一圈我們又回來(lái)了爪模。。
我們繼續(xù)往下看:當(dāng)解析錯(cuò)誤荚藻,我們直接調(diào)用傳進(jìn)來(lái)的fauler的Block失敗返回了屋灌,這里有一個(gè)
self.completionQueue
,這個(gè)是我們自定義的,這個(gè)是一個(gè)GCD的Queue如果設(shè)置了那么從這個(gè)Queue中回調(diào)結(jié)果应狱,否則從主隊(duì)列回調(diào)共郭。實(shí)際上這個(gè)Queue還是挺有用的,之前還用到過(guò)疾呻。我們公司有自己的一套數(shù)據(jù)加解密的解析模式除嘹,所以我們回調(diào)回來(lái)的數(shù)據(jù)并不想是主線程,我們可以設(shè)置這個(gè)Queue,在分線程進(jìn)行解析數(shù)據(jù)岸蜗,然后自己再調(diào)回到主線程去刷新UI尉咕。
言歸正傳,我們接著調(diào)用了父類的生成task的方法散吵,并且執(zhí)行了一個(gè)成功和失敗的回調(diào)龙考,我們接著去父類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對(duì)象炎功。
//其實(shí)現(xiàn)應(yīng)該是因?yàn)閕OS 8.0以下版本中會(huì)并發(fā)地創(chuàng)建多個(gè)task對(duì)象,而同步有沒(méi)有做好缓溅,導(dǎo)致taskIdentifiers 不唯一…這邊做了一個(gè)串行處理蛇损,串行隊(duì)列里面還有用GCD創(chuàng)建了個(gè)單例
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
- 我們注意到這個(gè)方法非常簡(jiǎn)單,就調(diào)用了一個(gè)
url_session_manager_create_task_safely()
函數(shù)坛怪,傳了一個(gè)Block進(jìn)去淤齐,Block里就是iOS原生生成dataTask的方法。此外袜匿,還調(diào)用了一個(gè)addDelegateForDataTask
的方法更啄。 - 我們到這先到這個(gè)函數(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居灯,因?yàn)槭窍胍骶€程等在這祭务,等執(zhí)行完,在返回怪嫌,因?yàn)楸仨殘?zhí)行完dataTask才有數(shù)據(jù)义锥,傳值才有意義。
//第二岩灭,為什么要用串行隊(duì)列拌倍,因?yàn)檫@塊是為了防止ios8以下內(nèi)部的dataTaskWithRequest是并發(fā)創(chuàng)建的,
//這樣會(huì)導(dǎo)致taskIdentifiers這個(gè)屬性值不唯一噪径,因?yàn)楹罄m(xù)要用taskIdentifiers來(lái)作為Key對(duì)應(yīng)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)境下,也不會(huì)創(chuàng)建其他隊(duì)列
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;
}
- 方法非常簡(jiǎn)單熄云,關(guān)鍵是理解這么做的目的:為什么我們不直接去調(diào)用
dataTask = [self.session dataTaskWithRequest:request];
非要繞這么一圈膨更,我們點(diǎn)進(jìn)去bug日志里看看,原來(lái)這是為了適配iOS8的以下缴允,創(chuàng)建session的時(shí)候荚守,偶發(fā)的情況會(huì)出現(xiàn)session的屬性taskIdentifier這個(gè)值不唯一,而這個(gè)taskIdentifier是我們后面來(lái)映射delegate的key,所以它必須是唯一的练般。 - 具體原因應(yīng)該是NSURLSession內(nèi)部去生成task的時(shí)候是用多線程并發(fā)去執(zhí)行的矗漾。想通了這一點(diǎn),我們就很好解決了薄料,我們只需要在iOS8以下同步串行的去生成task就可以防止這一問(wèn)題發(fā)生(如果還是不理解同步串行的原因敞贡,可以看看注釋)。
- 題外話:很多同學(xué)都會(huì)抱怨為什么sync我從來(lái)用不到摄职,看誊役,有用到的地方了吧获列,很多東西不是沒(méi)用,而只是你想不到怎么用蛔垢。
我們接著看到:
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
調(diào)用到:
- (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建立相互關(guān)系
delegate.manager = self;
delegate.completionHandler = completionHandler;
//這個(gè)taskDescriptionForSessionTasks用來(lái)發(fā)送開(kāi)始和掛起通知的時(shí)候會(huì)用到,就是用這個(gè)值來(lái)Post通知击孩,來(lái)兩者對(duì)應(yīng)
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
// ***** 將AF delegate對(duì)象與 dataTask建立關(guān)系
[self setDelegate:delegate forTask:dataTask];
// 設(shè)置AF delegate的上傳進(jìn)度,下載進(jìn)度塊鹏漆。
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
- 總結(jié)一下:
1)這個(gè)方法巩梢,生成了一個(gè)AFURLSessionManagerTaskDelegate
,這個(gè)其實(shí)就是AF的自定義代理。我們請(qǐng)求傳來(lái)的參數(shù)艺玲,都賦值給這個(gè)AF的代理了括蝠。
2)delegate.manager = self;
代理把AFURLSessionManager這個(gè)類作為屬性了,我們可以看到:
@property (nonatomic, weak) AFURLSessionManager *manager;
這個(gè)屬性是弱引用的,所以不會(huì)存在循環(huán)引用的問(wèn)題饭聚。
3)我們調(diào)用了[self setDelegate:delegate forTask:dataTask];
我們進(jìn)去看看這個(gè)方法做了什么:
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
//斷言忌警,如果沒(méi)有這個(gè)參數(shù),debug下crash在這
NSParameterAssert(task);
NSParameterAssert(delegate);
//加鎖保證字典線程安全
[self.lock lock];
// 將AF delegate放入以taskIdentifier標(biāo)記的詞典中(同一個(gè)NSURLSession中的taskIdentifier是唯一的)
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
// 為AF delegate 設(shè)置task 的progress監(jiān)聽(tīng)
[delegate setupProgressForTask:task];
//添加task開(kāi)始和暫停的通知
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
- 這個(gè)方法主要就是把AF代理和task建立映射若治,存在了一個(gè)我們事先聲明好的字典里慨蓝。
- 而要加鎖的原因是因?yàn)楸旧砦覀冞@個(gè)字典屬性是mutable的碟刺,是線程不安全的疏唾。而我們對(duì)這些方法的調(diào)用幸斥,確實(shí)是會(huì)在復(fù)雜的多線程環(huán)境中,后面會(huì)仔細(xì)提到線程問(wèn)題婆跑。
- 還有個(gè)
[delegate setupProgressForTask:task];
我們到方法里去看看:
- (void)setupProgressForTask:(NSURLSessionTask *)task {
__weak __typeof__(task) weakTask = task;
//拿到上傳下載期望的數(shù)據(jù)大小
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
//將上傳與下載進(jìn)度和 任務(wù)綁定在一起,直接cancel suspend resume進(jìn)度條庭呜,可以cancel...任務(wù)
[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這兩個(gè)屬性
[self.downloadProgress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
[self.uploadProgress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
}
- 這個(gè)方法也非常簡(jiǎn)單滑进,主要做了以下幾件事:
1)設(shè)置downloadProgress
與uploadProgress
的一些屬性,并且把兩者和task的任務(wù)狀態(tài)綁定在了一起募谎。注意這兩者都是NSProgress的實(shí)例對(duì)象扶关,(這里可能又一群小伙伴楞在這了,這是個(gè)什么...)簡(jiǎn)單來(lái)說(shuō)数冬,這就是iOS7引進(jìn)的一個(gè)用來(lái)管理進(jìn)度的類节槐,可以開(kāi)始,暫停拐纱,取消铜异,完整的對(duì)應(yīng)了task的各種狀態(tài),當(dāng)progress進(jìn)行各種操作的時(shí)候秸架,task也會(huì)引發(fā)對(duì)應(yīng)操作揍庄。
2)給task和progress的各個(gè)屬及添加KVO監(jiān)聽(tīng),至于監(jiān)聽(tīng)了干什么用东抹,我們接著往下看:
- (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]]) {
//給進(jìn)度條賦新值
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];
}
}
//上面的賦新值會(huì)觸發(fā)這兩個(gè)蚂子,調(diào)用block回調(diào)沃测,用戶拿到進(jìn)度
else if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}
方法非常簡(jiǎn)單直觀,主要就是如果task觸發(fā)KVO,則給progress進(jìn)度賦值食茎,應(yīng)為賦值了芽突,所以會(huì)觸發(fā)progress的KVO,也會(huì)調(diào)用到這里董瞻,然后去執(zhí)行我們傳進(jìn)來(lái)的
downloadProgressBlock
和uploadProgressBlock
寞蚌。主要的作用就是為了讓進(jìn)度實(shí)時(shí)的傳遞。主要是觀摩一下大神的寫代碼的結(jié)構(gòu)钠糊,這個(gè)解耦的編程思想挟秤,不愧是大神...
還有一點(diǎn)需要注意:我們之前的setProgress和這個(gè)KVO監(jiān)聽(tīng),都是在我們AF自定義的delegate內(nèi)的抄伍,是有一個(gè)task就會(huì)有一個(gè)delegate的艘刚。所以說(shuō)我們是每個(gè)task都會(huì)去監(jiān)聽(tīng)這些屬性,分別在各自的AF代理內(nèi)截珍。看到這攀甚,可能有些小伙伴會(huì)有點(diǎn)亂,沒(méi)關(guān)系岗喉。等整個(gè)講完之后我們還會(huì)詳細(xì)的去講捋一捋manager秋度、task、還有AF自定義代理三者之前的對(duì)應(yīng)關(guān)系钱床。
到這里我們整個(gè)對(duì)task的處理就完成了荚斯。
接著task就開(kāi)始請(qǐng)求網(wǎng)絡(luò)了,還記得我們初始化方法中:
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
我們把AFUrlSessionManager作為了所有的task的delegate查牌。當(dāng)我們請(qǐng)求網(wǎng)絡(luò)的時(shí)候事期,這些代理開(kāi)始調(diào)用了:
AFUrlSessionManager一共實(shí)現(xiàn)了如上圖所示這么一大堆NSUrlSession相關(guān)的代理。(小伙伴們的順序可能不一樣纸颜,樓主根據(jù)代理隸屬重新排序了一下)
而只轉(zhuǎn)發(fā)了其中3條到AF自定義的delegate中:
這就是我們一開(kāi)始說(shuō)的兽泣,AFUrlSessionManager對(duì)這一大堆代理做了一些公共的處理,而轉(zhuǎn)發(fā)到AF自定義代理的3條胁孙,則負(fù)責(zé)把每個(gè)task對(duì)應(yīng)的數(shù)據(jù)回調(diào)出去唠倦。
又有小伙伴問(wèn)了,我們?cè)O(shè)置的這個(gè)代理不是NSURLSessionDelegate
嗎浊洞?怎么能響應(yīng)NSUrlSession這么多代理呢牵敷?我們點(diǎn)到類的聲明文件中去看看:
@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)用毛肋。 - 而AF還重寫了
respondsToSelector
方法:
- (BOOL)respondsToSelector:(SEL)selector {
//復(fù)寫了selector的方法怨咪,這幾個(gè)方法是在本類有實(shí)現(xiàn)的,但是如果外面的Block沒(méi)賦值的話润匙,則返回NO诗眨,相當(dāng)于沒(méi)有實(shí)現(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];
}
這樣如果沒(méi)實(shí)現(xiàn)這些我們自定義的Block也不會(huì)去回調(diào)這些代理孕讳。因?yàn)楸旧砟承┐斫吵粓?zhí)行了這些自定義的Block,如果Block都沒(méi)有賦值厂财,那我們調(diào)用代理也沒(méi)有任何意義芋簿。
講到這,我們順便看看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;
各自對(duì)應(yīng)的還有一堆這樣的set方法:
- (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block {
self.sessionDidBecomeInvalid = block;
}
方法都是一樣的璃饱,就不重復(fù)粘貼占篇幅了与斤。
主要談?wù)勥@個(gè)設(shè)計(jì)思路
- 作者用@property把這個(gè)些Block屬性在.m文件中聲明,然后復(fù)寫了set方法。
- 然后在.h中去聲明這些set方法:
- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;
為什么要繞這么一大圈呢荚恶?原來(lái)這是為了我們這些用戶使用起來(lái)方便撩穿,調(diào)用set方法去設(shè)置這些Block,能很清晰的看到Block的各個(gè)參數(shù)與返回值谒撼。大神的精髓的編程思想無(wú)處不體現(xiàn)...
接下來(lái)我們就講講這些代理方法做了什么(按照順序來(lái)):
NSURLSessionDelegate
代理1:
//當(dāng)前這個(gè)session已經(jīng)失效時(shí)食寡,該代理方法被調(diào)用。
/*
如果你使用finishTasksAndInvalidate函數(shù)使該session失效嗤栓,
那么session首先會(huì)先完成最后一個(gè)task冻河,然后再調(diào)用URLSession:didBecomeInvalidWithError:代理方法,
如果你調(diào)用invalidateAndCancel方法來(lái)使session失效茉帅,那么該session會(huì)立即調(diào)用上面的代理方法。
*/
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
if (self.sessionDidBecomeInvalid) {
self.sessionDidBecomeInvalid(session, error);
}
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}
- 方法調(diào)用時(shí)機(jī)注釋寫的很清楚锭弊,就調(diào)用了一下我們自定義的Block,還發(fā)了一個(gè)失效的通知堪澎,至于這個(gè)通知有什么用。很抱歉味滞,AF沒(méi)用它做任何事樱蛤,只是發(fā)了...目的是用戶自己可以利用這個(gè)通知做什么事吧。
- 其實(shí)AF大部分通知都是如此剑鞍。當(dāng)然昨凡,還有一部分通知AF還是有自己用到的,包括配合對(duì)UIKit的一些擴(kuò)展來(lái)使用蚁署,后面我們會(huì)有單獨(dú)篇幅展開(kāi)講講這些UIKit的擴(kuò)展類的實(shí)現(xiàn)便脊。
代理2:
//2、https認(rèn)證
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
//挑戰(zhàn)處理類型為 默認(rèn)
/*
NSURLSessionAuthChallengePerformDefaultHandling:默認(rèn)方式處理
NSURLSessionAuthChallengeUseCredential:使用指定的證書
NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰(zhàn)
*/
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
// sessionDidReceiveAuthenticationChallenge是自定義方法光戈,用來(lái)如何應(yīng)對(duì)服務(wù)器端的認(rèn)證挑戰(zhàn)
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
// 此處服務(wù)器要求客戶端的接收認(rèn)證挑戰(zhàn)方法是NSURLAuthenticationMethodServerTrust
// 也就是說(shuō)服務(wù)器端需要客戶端返回一個(gè)根據(jù)認(rèn)證挑戰(zhàn)的保護(hù)空間提供的信任(即challenge.protectionSpace.serverTrust)產(chǎn)生的挑戰(zhàn)證書哪痰。
// 而這個(gè)證書就需要使用credentialForTrust:來(lái)創(chuàng)建一個(gè)NSURLCredential對(duì)象
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 基于客戶端的安全策略來(lái)決定是否信任該服務(wù)器遂赠,不信任的話,也就沒(méi)必要響應(yīng)挑戰(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 {
//默認(rèn)挑戰(zhàn) 唯一區(qū)別晌杰,下面少了這一步跷睦!
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
//取消挑戰(zhàn)
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
//默認(rèn)挑戰(zhàn)方式
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
//完成挑戰(zhàn)
if (completionHandler) {
completionHandler(disposition, credential);
}
}
- 函數(shù)作用:
web服務(wù)器接收到客戶端請(qǐng)求時(shí),有時(shí)候需要先驗(yàn)證客戶端是否為正常用戶肋演,再?zèng)Q定是夠返回真實(shí)數(shù)據(jù)抑诸。這種情況稱之為服務(wù)端要求客戶端接收挑戰(zhàn)(NSURLAuthenticationChallenge *challenge)。接收到挑戰(zhàn)后爹殊,客戶端要根據(jù)服務(wù)端傳來(lái)的challenge來(lái)生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential(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)。
- 函數(shù)討論:
該代理方法會(huì)在下面兩種情況調(diào)用:
- 當(dāng)服務(wù)器端要求客戶端提供證書時(shí)或者進(jìn)行NTLM認(rèn)證(Windows NT LAN Manager绒瘦,微軟提出的WindowsNT挑戰(zhàn)/響應(yīng)驗(yàn)證機(jī)制)時(shí)称簿,此方法允許你的app提供正確的挑戰(zhàn)證書。
- 當(dāng)某個(gè)session使用SSL/TLS協(xié)議惰帽,第一次和服務(wù)器端建立連接的時(shí)候憨降,服務(wù)器會(huì)發(fā)送給iOS客戶端一個(gè)證書,此方法允許你的app驗(yàn)證服務(wù)期端的證書鏈(certificate keychain)
注:如果你沒(méi)有實(shí)現(xiàn)該方法该酗,該session會(huì)調(diào)用其NSURLSessionTaskDelegate的代理方法URLSession:task:didReceiveChallenge:completionHandler: 授药。
這里,我把官方文檔對(duì)這個(gè)方法的描述翻譯了一下呜魄。
總結(jié)一下悔叽,這個(gè)方法其實(shí)就是做https認(rèn)證的【粜幔看看上面的注釋娇澎,大概能看明白這個(gè)方法做認(rèn)證的步驟,我們還是如果有自定義的做認(rèn)證的Block睹晒,則調(diào)用我們自定義的趟庄,否則去執(zhí)行默認(rèn)的認(rèn)證步驟,最后調(diào)用完成認(rèn)證:
//完成挑戰(zhàn)
if (completionHandler) {
completionHandler(disposition, credential);
}
代理3:
//3伪很、 當(dāng)session中所有已經(jīng)入隊(duì)的消息被發(fā)送出去后戚啥,會(huì)調(diào)用該代理方法。
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
if (self.didFinishEventsForBackgroundURLSession) {
dispatch_async(dispatch_get_main_queue(), ^{
self.didFinishEventsForBackgroundURLSession(session);
});
}
}
官方文檔翻譯:
函數(shù)討論:
- 在iOS中锉试,當(dāng)一個(gè)后臺(tái)傳輸任務(wù)完成或者后臺(tái)傳輸時(shí)需要證書猫十,而此時(shí)你的app正在后臺(tái)掛起,那么你的app在后臺(tái)會(huì)自動(dòng)重新啟動(dòng)運(yùn)行,并且這個(gè)app的UIApplicationDelegate會(huì)發(fā)送一個(gè)application:handleEventsForBackgroundURLSession:completionHandler:消息炫彩。該消息包含了對(duì)應(yīng)后臺(tái)的session的identifier匾七,而且這個(gè)消息會(huì)導(dǎo)致你的app啟動(dòng)。你的app隨后應(yīng)該先存儲(chǔ)completion handler江兢,然后再使用相同的identifier創(chuàng)建一個(gè)background configuration昨忆,并根據(jù)這個(gè)background configuration創(chuàng)建一個(gè)新的session。這個(gè)新創(chuàng)建的session會(huì)自動(dòng)與后臺(tái)任務(wù)重新關(guān)聯(lián)在一起杉允。
- 當(dāng)你的app獲取了一個(gè)URLSessionDidFinishEventsForBackgroundURLSession:消息邑贴,這就意味著之前這個(gè)session中已經(jīng)入隊(duì)的所有消息都轉(zhuǎn)發(fā)出去了,這時(shí)候再調(diào)用先前存取的completion handler是安全的叔磷,或者因?yàn)閮?nèi)部更新而導(dǎo)致調(diào)用completion handler也是安全的拢驾。
NSURLSessionTaskDelegate
代理4:
//被服務(wù)器重定向的時(shí)候調(diào)用
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
NSURLRequest *redirectRequest = request;
// step1\. 看是否有對(duì)應(yīng)的user block 有的話轉(zhuǎn)發(fā)出去,通過(guò)這4個(gè)參數(shù)改基,返回一個(gè)NSURLRequest類型參數(shù)繁疤,request轉(zhuǎn)發(fā)、網(wǎng)絡(luò)重定向.
if (self.taskWillPerformHTTPRedirection) {
//用自己自定義的一個(gè)重定向的block實(shí)現(xiàn)秕狰,返回一個(gè)新的request稠腊。
redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
}
if (completionHandler) {
// step2\. 用request重新請(qǐng)求
completionHandler(redirectRequest);
}
}
- 一開(kāi)始我以為這個(gè)方法是類似
NSURLProtocol
,可以在請(qǐng)求時(shí)自己主動(dòng)的去重定向request鸣哀,后來(lái)發(fā)現(xiàn)不是架忌,這個(gè)方法是在服務(wù)器去重定向的時(shí)候,才會(huì)被調(diào)用我衬。為此我寫了段簡(jiǎn)單的PHP測(cè)了測(cè):
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Welcome extends CI_Controller {
public function index()
{
header("location: http://www.huixionghome.cn/");
}
}
證實(shí)確實(shí)如此叹放,當(dāng)我們服務(wù)器重定向的時(shí)候,代理就被調(diào)用了挠羔,我們可以去重新定義這個(gè)重定向的request井仰。
- 關(guān)于這個(gè)代理還有一些需要注意的地方:
此方法只會(huì)在default session或者ephemeral session中調(diào)用,而在background session中褥赊,session task會(huì)自動(dòng)重定向糕档。
這里指的模式是我們一開(kāi)始Init的模式:
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
這個(gè)模式總共分為3種:
對(duì)于NSURLSession對(duì)象的初始化需要使用NSURLSessionConfiguration,而NSURLSessionConfiguration有三個(gè)類工廠方法:
+defaultSessionConfiguration 返回一個(gè)標(biāo)準(zhǔn)的 configuration拌喉,這個(gè)配置實(shí)際上與 NSURLConnection 的網(wǎng)絡(luò)堆棧(networking stack)是一樣的,具有相同的共享 NSHTTPCookieStorage俐银,共享 NSURLCache 和共享NSURLCredentialStorage尿背。
+ephemeralSessionConfiguration 返回一個(gè)預(yù)設(shè)配置,這個(gè)配置中不會(huì)對(duì)緩存捶惜,Cookie 和證書進(jìn)行持久性的存儲(chǔ)田藐。這對(duì)于實(shí)現(xiàn)像秘密瀏覽這種功能來(lái)說(shuō)是很理想的。
+backgroundSessionConfiguration:(NSString *)identifier 的獨(dú)特之處在于,它會(huì)創(chuàng)建一個(gè)后臺(tái) session汽久。后臺(tái) session 不同于常規(guī)的鹤竭,普通的 session,它甚至可以在應(yīng)用程序掛起景醇,退出或者崩潰的情況下運(yùn)行上傳和下載任務(wù)臀稚。初始化時(shí)指定的標(biāo)識(shí)符,被用于向任何可能在進(jìn)程外恢復(fù)后臺(tái)傳輸?shù)氖刈o(hù)進(jìn)程(daemon)提供上下文三痰。
代理5:
//https認(rèn)證
- (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);
}
}
- 鑒于篇幅吧寺,就不去貼官方文檔的翻譯了,大概總結(jié)一下:
之前我們也有一個(gè)https認(rèn)證散劫,功能一樣稚机,執(zhí)行的內(nèi)容也完全一樣。 - 區(qū)別在于這個(gè)是non-session-level級(jí)別的認(rèn)證获搏,而之前的是session-level級(jí)別的赖条。
- 相對(duì)于它,多了一個(gè)參數(shù)task,然后調(diào)用我們自定義的Block會(huì)多回傳這個(gè)task作為參數(shù)常熙,這樣我們就可以根據(jù)每個(gè)task去自定義我們需要的https認(rèn)證方式纬乍。
代理6:
//當(dāng)一個(gè)session task需要發(fā)送一個(gè)新的request body stream到服務(wù)器端的時(shí)候,調(diào)用該代理方法症概。
- (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);
}
}
- 該代理方法會(huì)在下面兩種情況被調(diào)用:
- 如果task是由uploadTaskWithStreamedRequest:創(chuàng)建的,那么提供初始的request body stream時(shí)候會(huì)調(diào)用該代理方法彼城。
- 因?yàn)檎J(rèn)證挑戰(zhàn)或者其他可恢復(fù)的服務(wù)器錯(cuò)誤诅蝶,而導(dǎo)致需要客戶端重新發(fā)送一個(gè)含有body stream的request,這時(shí)候會(huì)調(diào)用該代理募壕。
代理7:
/*
//周期性地通知代理發(fā)送到服務(wù)器端數(shù)據(jù)的進(jìn)度调炬。
*/
- (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ù)給服務(wù)器舱馅,會(huì)回調(diào)這個(gè)方法缰泡,通知已經(jīng)發(fā)送了多少,總共要發(fā)送多少代嗤。
- 代理方法里也就是僅僅調(diào)用了我們自定義的Block而已棘钞。
未完總結(jié):
- 其實(shí)寫了這么多,還沒(méi)有講到真正重要的地方干毅,但是因?yàn)橐呀?jīng)接近簡(jiǎn)書最大篇幅宜猜,所以只能先在這里結(jié)個(gè)尾了。
- 如果能看到這里硝逢,說(shuō)明你是個(gè)非常有耐心姨拥,非常好學(xué)绅喉,非常nice的iOS開(kāi)發(fā)。樓主為你點(diǎn)個(gè)贊叫乌。那么相信你也不吝嗇手指動(dòng)一動(dòng)柴罐,給本文點(diǎn)個(gè)喜歡...順便關(guān)注一下樓主...畢竟寫了這么多...也很辛苦...咳咳,我不小心說(shuō)出心聲了么憨奸?
- 最后革屠,萬(wàn)一如果本文有人轉(zhuǎn)載,麻煩注明出處~謝謝膀藐!
參考:
iOS--AFN實(shí)現(xiàn)原理
AFNetworking到底做了什么屠阻?(終)
AFNetworking到底做了什么(二)?
AFNetworking之于https認(rèn)證
AFNetworking之UIKit擴(kuò)展與緩存實(shí)現(xiàn)
iOS 網(wǎng)絡(luò)框架-AFNetworking 3.1.0 源碼解讀
AFNetworking的基本使用