IOS源碼解析:AFNetworking

原創(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ò)通信模塊(AFURLSessionManagerAFHTTPSessionManger)
  • 網(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)系圖如下所示:

AF架構(gòu)圖

二、初始化方法

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的核心類AFURLSessionManagerGet請(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 methodURLString來構(gòu)建一個(gè)NSMutableURLRequest對(duì)象實(shí)例。如果methodGETHEAD、DELETE,那parameter將會(huì)被用來構(gòu)建一個(gè)基于url編碼的查詢字符串(query url),并且這個(gè)字符串會(huì)直接加到requesturl后面。對(duì)于其他的Method,比如POST/PUT,它們會(huì)根據(jù)parameterEncoding屬性進(jìn)行編碼,而后加到requesthttp 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è)置HTTPShouldHandleCookiesYES,就處理存儲(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è)屬性:fieldvalue。方法: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)用并解析,你不能保證NSDictionaryvalue中存放的不是一個(gè)NSArrayNSSet。

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作為了所有的taskdelegate。

- (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所需的NSURLSessionAuthChallengeDispositionNSURLCredential。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

參考文獻(xiàn)

AFNetworking到底做了什么?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容