AFNetworking是一個(gè)非常簡潔的框架,關(guān)于基本架構(gòu),可以看看這篇文章,本文主要闡述AFNetworking在設(shè)計(jì)上是如何對(duì)NSURLSession封裝的。本文大致分為兩個(gè)部分,第一個(gè)部分為NSURLSession的設(shè)計(jì),第二個(gè)部分為AFNetworking的封裝設(shè)計(jì)
一、NSURLSession設(shè)計(jì)
NSURLSession主要由這幾個(gè)部分組成:
- NSURLSession
- NSURLSessionTask(擁有三種子類)
- NSURLSessionConfiguration
- 代理方法
首先我們通過一段Session的使用代碼來看各部分之間的關(guān)系:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; // #1
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
delegate:self
delegateQueue:[NSOperationQueue mainQueue]]; // #2
NSURLSessionDataTask *task = [session dataTaskWithURL:[[NSURL alloc]initWithString:@""]]; // #3
[task resume];
為了方便理解這幾個(gè)部分之間的關(guān)系,這段代碼采用了delegate進(jìn)行回調(diào)處理
- #1:創(chuàng)建了NSURLSessionConfiguration對(duì)象,該對(duì)象的工廠模式方法提供了三種對(duì)象
#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
@property (class, readonly, strong) NSURLSessionConfiguration *defaultSessionConfiguration;
@property (class, readonly, strong) NSURLSessionConfiguration *ephemeralSessionConfiguration;
#endif
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier NS_AVAILABLE(10_10, 8_0);
以上有三種方法,下面簡要介紹下每種類型的特點(diǎn)
- defaultSessionConfiguration:默認(rèn)的配置,和NSURLConnection的配置類似,使用硬盤來緩存數(shù)據(jù)(不同的是NSURLConnection的配置是全局的)
- ephemeralSessionConfiguration:不會(huì)將Cookie、緩存等存儲(chǔ)到磁盤,而是放在內(nèi)存中,程序退出時(shí)數(shù)據(jù)會(huì)消失(可以用于私密瀏覽)
- backgroundSessionConfigurationWithIdentifier:可以在應(yīng)用程序掛起、退出、崩潰的情況下運(yùn)行下載和上傳任務(wù),會(huì)在后臺(tái)另外開啟一個(gè)線程,但是系統(tǒng)會(huì)根據(jù)負(fù)載程度去調(diào)度這個(gè)線程的操作,可能會(huì)造成速度緩慢或者超時(shí)
三種工廠提供了三種對(duì)象具有不同的特點(diǎn),除此之外,NSURLSessionConfiguration擁有很多的屬性可以進(jìn)行配置

這里列出一些常用屬性:
-
@property NSTimeInterval timeoutIntervalForRequest;:請(qǐng)求超時(shí) -
@property NSTimeInterval timeoutIntervalForResource;:資源超時(shí) -
@property (nullable, copy) NSDictionary *HTTPAdditionalHeaders;:請(qǐng)求頭,配置如下
configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json",
@"Accept-Language": @"en",
@"Accept-Encoding": @"",
@"Authorization": @"",
@"Connection": @"",
@"User-Agent": @""};
可以發(fā)現(xiàn)字典中的key都是標(biāo)準(zhǔn)的HTTP請(qǐng)求頭的key,可以通過這種方式對(duì)請(qǐng)求頭進(jìn)行自定義配置
-
#2:創(chuàng)建了NSURLSession對(duì)象,依賴于三個(gè)參數(shù)
- NSURLSessionConfiguration:配置
- Delegate:代理對(duì)象
- DelegateQueue:代理隊(duì)列,在NSURLConnection中往往需要指定代理隊(duì)列,代表回調(diào)方法在哪個(gè)線程中執(zhí)行,NSURLSession提供了類初始化方法,省略了代理隊(duì)列的指定,默認(rèn)為主線程的主隊(duì)列
除了這種方式之外,還可以通過單例模式提供的類方法創(chuàng)建,內(nèi)部實(shí)現(xiàn)可能沒有設(shè)置代理和代理隊(duì)列,采用的是默認(rèn)配置
@property (class, readonly, strong) NSURLSession *sharedSession;
這里的session使用了delegate回調(diào),蘋果還提供了block回調(diào)的形式:
NSURLSessionDataTask *task = [session dataTaskWithURL:[[NSURL alloc]initWithString:@""]
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
-
#3:創(chuàng)建NSURLSessionTask對(duì)象,NSURLSession提供了這幾種task類型
- NSURLSessionTask:超類,一般不具體使用
- NSURLSessionDataTask:請(qǐng)求
- NSURLSessionUploadTask:上傳
- NSURLSessionDownloadTask:下載
它們之間的繼承關(guān)系如下:
總結(jié):NSURLSession的設(shè)計(jì)主要有三個(gè)部分,三個(gè)部分相互獨(dú)立又具有聯(lián)系,NSURLSessionConfiguration進(jìn)行配置管理。NSURLSession將配置,代理,代理隊(duì)列等對(duì)象關(guān)聯(lián),用于創(chuàng)建任務(wù)。NSURLSessionTask是任務(wù)類,其對(duì)象具有操作該任務(wù)的各種方法,啟動(dòng),暫停等,同時(shí)任務(wù)的回調(diào)提供兩種方式,block和代理。
二、AFNetworking的封裝設(shè)計(jì)
這里先回憶一下在基本架構(gòu)這篇文章中所提到的AFNetworking的使用代碼被分為兩個(gè)部分,第一個(gè)部分是初始化AFHTTPSessionManager對(duì)象,第二個(gè)部分是調(diào)用請(qǐng)求方法。
先來看第一個(gè)部分,我們?cè)俅位仡櫼幌路椒ǖ恼{(diào)用棧
- [AFHTTPSessionManager initWithBaseURL:]
- [AFHTTPSessionManager initWithBaseURL:sessionConfiguration:] // #1
- [AFURLSessionManager initWithSessionConfiguration:] // #2
- [NSURLSession sessionWithConfiguration:delegate:delegateQueue:]
- [AFJSONResponseSerializer serializer]
- [AFSecurityPolicy defaultPolicy]
- [AFNetworkReachabilityManager sharedManager]
- [AFHTTPRequestSerializer serializer]
- [AFJSONResponseSerializer serializer]
我們順著方法調(diào)用棧,看方法具體實(shí)現(xiàn)細(xì)節(jié),從而理解AFNetworking是如何封裝NSURLSession的
- #1:
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
self = [super initWithSessionConfiguration:configuration]; // &1
if (!self) {
return nil;
}
// Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
self.baseURL = url; // &2
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
&1:這里調(diào)用了父類AFURLSessionManager的初始化方法
&2:設(shè)置了baseURL
- #2:
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; // &1
}
self.sessionConfiguration = configuration;
// &2
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue]; // &3
// &4
self.responseSerializer = [AFJSONResponseSerializer serializer];
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
// &5
[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;
}
&1:確定配置對(duì)象(原生調(diào)用)
&2:設(shè)置一個(gè)隊(duì)列,并設(shè)置為串行(最大并發(fā)為1),在&3中創(chuàng)建session的時(shí)候作為參數(shù)(由于AFNetworking將AFURLSessionManager類作為了NSURLSession的代理,所以這里另外添加一個(gè)在子線程中的操作隊(duì)列)
&3:創(chuàng)建NSURLSession對(duì)象(原生調(diào)用)
&4:設(shè)置AFNetworking中的序列化和安全策略(AF自己的模塊封裝)
&5:為每個(gè)task添加一個(gè)AF封裝的Delegate,后文會(huì)提到
-
總結(jié)第一部分
從上面的代碼細(xì)節(jié)來看,AFHTTPSessionManager的初始化操作就是做了這些事情:- 獲得了NSURLSessionConfiguration對(duì)象
- 創(chuàng)建了NSURLSession對(duì)象,并設(shè)置自身為代理類,添加了操作隊(duì)列
- 設(shè)置了AF自己封裝的序列化和安全策略
這樣一來,就很容易看出,其實(shí)就是在原生的初始化操作上添加了一些AF自己封裝的策略對(duì)象
接下來再看看第二部分,同樣回顧一下調(diào)用棧
使用GET:parameters:process:success:failure:方法作為例子來查看一下源碼實(shí)現(xiàn)
- [AFHTTPSessionManager GET:parameters:process:success:failure:] // #1
- [AFHTTPSessionManager dataTaskWithHTTPMethod:parameters:uploadProgress:downloadProgress:success:failure:] // #2
- [AFHTTPRequestSerializer requestWithMethod:URLString:parameters:error:]
- [AFURLSessionManager dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:] // #3
- [NSURLSession dataTaskWithRequest:]
- [AFURLSessionManager addDelegateForDataTask:uploadProgress:downloadProgress:completionHandler:] // #4
- [AFURLSessionManagerTaskDelegate init]
- [AFURLSessionManager setDelegate:forTask:] // #5
- [NSURLSessionDataTask resume]
- #1:
- (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
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
return dataTask;
}
從表層能夠看出返回了一個(gè)NSURLSessionDataTask對(duì)象,并且調(diào)用了resume操作(和原生一樣),我們接著看下返回對(duì)象的方法是如何實(shí)現(xiàn)的
- #2:
- (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;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError]; // &1
if (serializationError) {
if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
}
return nil;
}
// &2
__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:創(chuàng)建了NSURLRequest,可以發(fā)現(xiàn)AF內(nèi)部是通過Request的方式創(chuàng)建的task,而不是URL
&2:調(diào)用另外一個(gè)方法返回task對(duì)象
- #3:
- (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;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request]; // &1
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler]; // &2
return dataTask;
}
&1:這部分就很熟悉了,利用原生的方式通過request來創(chuàng)建task對(duì)象,然后返回。到這里,就已經(jīng)能明白關(guān)于task對(duì)象是如何被封裝返回的
&2:按照名字來看,這里好像是為task對(duì)象添加代理方法的,我們繼續(xù)往下研究
- #4:
- (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
{
// &1
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask]; // &2
// &3
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
&1:新建了一個(gè)AFURLSessionManagerTaskDelegate對(duì)象,是AF自己封裝的代理對(duì)象
&2:看樣子還需要進(jìn)一步查看設(shè)置代理的細(xì)節(jié),這一個(gè)方法傳入了代理對(duì)象和task對(duì)象,我們繼續(xù)看
&3:將block賦值給delegate的block屬性,方便回調(diào)
- #5:
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock]; // &1
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; // &2
[delegate setupProgressForTask:task];
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
&1:有個(gè)加鎖操作,保證線程安全
&2:用task的taskIdentifiier屬性為key,delegate為value來進(jìn)行對(duì)應(yīng),到這里,可以知道AF是用字典將delegate和task一一對(duì)應(yīng)的
-
總結(jié)第二部分:
通過上面的分析,可以看出第二部分實(shí)際上就是利用NSURLRequest去創(chuàng)建NSURLSessionTask對(duì)象。同時(shí)呢,AFURLSessionManager作為了NSURLSession的代理,AF內(nèi)部自定義了一個(gè)AFURLSessionManagerTaskDelegate代理類,該類具有很多block屬性。并且,AF在內(nèi)部實(shí)現(xiàn)了NSURLSession的代理方法,方法實(shí)現(xiàn)中實(shí)現(xiàn)block賦值,代理類的block屬性對(duì)外暴露,在合適的地方回調(diào)。AF將很多NSRULSession中的代理方法都變成block形式進(jìn)行暴露,更加簡潔。

