知名的iOS網(wǎng)絡(luò)框架 AFNetworking 3.0 發(fā)布一段時(shí)間了,現(xiàn)在來(lái)閱讀記錄一下。(注:我目前閱讀的版本是3.1.0)
AFNetworking 3.0 的改動(dòng)
AFNetworking 3.0 拋棄了基于 NSURLConnection
的API,全力支持 NSURLSession
, 在Xcode7中,蘋果明確說(shuō)明廢棄了 NSURLConnection
, 建議全面使用 NSURLSession
:
/*** DEPRECATED: The NSURLConnection class should no longer be used. NSURLSession is the replacement for NSURLConnection ***/
所以, AFURLConnectionOperation, AFHTTPRequestOperation , AFHTTPRequestOperationManager 這幾個(gè)2.0版本中極為關(guān)鍵的幾個(gè)類已被全部移除。
AFURLSessionManager
AFNetworking 3.0 中關(guān)鍵的幾個(gè)類:AFURLSessionManager
與 AFHTTPSessionManager
,先來(lái)看看 AFURLSessionManager
文件:
- AFURLSessionManager 的初始化,在 initWithSessionConfiguration 方法:
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
.....省略......
return self;
}
-
創(chuàng)建NSURLSession時(shí)需要設(shè)置NSURLSessionConfiguration,NSURLSessionConfiguration 有三種模式:
- 一般模式(default):工作模式類似于原來(lái)的NSURLConnection,可以使用緩存的Cache,Cookie,鑒權(quán)。
- 及時(shí)模式(ephemeral):不保存任何數(shù)據(jù)到磁盤,不使用緩存的Cache,Cookie,鑒權(quán)。
- 后臺(tái)模式(background):支持在后臺(tái)完成上傳下載 (需要iOS 8以上)
創(chuàng)建NSURLSession時(shí)還需要設(shè)置 delegate , delegate 用來(lái)處理請(qǐng)求中的各種事件,可以設(shè)置為nil使用系統(tǒng)提供的delegate,但是要想支持后臺(tái)傳輸數(shù)據(jù)必須提供自定義實(shí)現(xiàn)的delegate;另外,NSURLSession對(duì)象是強(qiáng)引用了delegate,如果app最終沒(méi)有調(diào)用 invalidateAndCancel 方法 來(lái)invalidate 該session的話,則會(huì)造成內(nèi)存泄漏。
創(chuàng)建NSURLSession時(shí)可以設(shè)置相應(yīng)的 OperationQueue, 決定請(qǐng)求過(guò)程中的一系列事件在哪個(gè) OperationQueue 回調(diào),這里是設(shè)置了最大并發(fā)量為1的隊(duì)列,也就相當(dāng)于串行隊(duì)列了。(AFNetworing 2.0 版本是設(shè)置了一條常駐線程來(lái)響應(yīng)所有網(wǎng)絡(luò)請(qǐng)求的delegate事件)
2 接著 AFURLSessionManager 當(dāng)然實(shí)現(xiàn)了 NSURLSession Delegate的各個(gè)接口,挺容易看的,還是看一看它暴露的創(chuàng)建請(qǐng)求任務(wù)的方法吧:
- (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];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
-
這里使用
url_session_manager_create_task_safely
的方法,是為了解決iOS8以下如果并發(fā)創(chuàng)建NSURLSessionTask時(shí)會(huì)出現(xiàn)的bug(源碼里有對(duì)應(yīng)的鏈接)。這時(shí)候獲取的 NSURLSessionDataTask 是出于掛起狀態(tài)的,還不會(huì)發(fā)起網(wǎng)絡(luò)請(qǐng)求。NSURLSessionTask 有三個(gè)職能不同的子類,- NSURLSessionDataTask: 用于一般的請(qǐng)求資源,以NSData對(duì)象的方式返回服務(wù)器響應(yīng)的數(shù)據(jù),它不支持backround session;
- NSURLSessionUploadTask: 用于上傳,支持backround session;
- NSURLSessionDownloadTask: 用于下載數(shù)據(jù)到文件中,也支持backround session。
我們?cè)倏纯蠢锩嬖O(shè)置task代理的方法:
- (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];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
針對(duì)于每個(gè)data task均有對(duì)應(yīng)的delegate,這個(gè)一對(duì)一關(guān)系是保存在一個(gè)字典中,以task的唯一標(biāo)志作為 key,并且取值賦值刪除的時(shí)候均要上鎖,確保線程安全;
再看一看自定義的 AFURLSessionManagerTaskDelegate 代理對(duì)象,它實(shí)現(xiàn)了不少功能:
1, 通過(guò)KVO的方式監(jiān)聽(tīng)上傳下載的進(jìn)度并回調(diào)出去;
2, 請(qǐng)求接收的數(shù)據(jù)不斷追加到 mutableData :
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
[self.mutableData appendData:data];
}
3, 設(shè)置當(dāng)前task完成后的回調(diào):
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;
它是在NSURLSession的代理方法中被調(diào)用的,
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
一開(kāi)始容易混淆這兩個(gè)回調(diào),其實(shí)是當(dāng)task完成數(shù)據(jù)傳輸后,會(huì)回調(diào)上面的方法,然后根據(jù)task從字典中取出對(duì)應(yīng)的 AFURLSessionManagerTaskDelegate 對(duì)象,然后 AFURLSessionManagerTaskDelegate 對(duì)象再進(jìn)行調(diào)用完成的callback:
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{.......省略部分.....
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
//We no longer need the reference, so nil it out to gain back some memory.
self.mutableData = nil;
}
.........省略............
if (error) {
.......省略........
} else {
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
}
1, 這里有個(gè)技巧,將可變的數(shù)據(jù)拷貝后,馬上將mutableData置為nil來(lái)釋放回收內(nèi)存,特別是處理大文件的時(shí)候效果就會(huì)出來(lái)了。具體描述請(qǐng)看這里.
2,它的流程是當(dāng)請(qǐng)求沒(méi)有出錯(cuò)時(shí),異步調(diào)用 block 處理序列化task的響應(yīng)對(duì)象,然后把數(shù)據(jù)對(duì)象再通過(guò)group異步回調(diào)出去.
其它:
a. NSStringFromSelector(_cmd)
_cmd表示當(dāng)前方法的selector,正如self代表了當(dāng)前方法調(diào)用的對(duì)象
// AFURLSessionManager中的方法:
- (NSArray *)tasks {
//這里的NSStringFromSelector(_cmd) 與 NSStringFromSelector(@selector(tasks)) 相等
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}
b. Method Swizzle
在AFURLSessionManager文件中,有一段代碼用方法混寫的方式修復(fù)了iOS7 iOS8 NSURLSessionTask
出現(xiàn)的問(wèn)題,方法混寫的知識(shí)已經(jīng)爛大街了,就不寫了,這個(gè)問(wèn)題具體再看源碼鏈接吧。
AFHTTPSessionManager
1, AFHTTPSessionManager
實(shí)現(xiàn)類就幾百行代碼,比較容易看,來(lái)看看它提供的GET請(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
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
return dataTask;
}
這里就是根據(jù)請(qǐng)求參數(shù)序列化創(chuàng)建請(qǐng)求對(duì)象設(shè)置進(jìn)度以及請(qǐng)求成功或失敗的回調(diào),然后返回dataTask給你并啟動(dòng),開(kāi)始真正的網(wǎng)絡(luò)請(qǐng)求了。
2,按照 AFNetworking 3.0 給出的遷移文檔中,可以簡(jiǎn)單使用 AFHTTPSessionManager 來(lái)發(fā)起GET請(qǐng)求:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"http://example.com/resources.json" parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
很熟悉是吧,跟2.0版本一樣,獲取manager后調(diào)用GET方法,但是來(lái)看看AFHTTPSessionManager
類的manager 方法:
+ (instancetype)manager {
return [[[self class] alloc] initWithBaseURL:nil];
}
問(wèn)題來(lái)了,如果這么用的話,AFHTTPSessionManager對(duì)象調(diào)用請(qǐng)求GET方法后,一直沒(méi)有被釋放,因?yàn)樗恢睆?qiáng)引用著session即NSURLSession對(duì)象,而session一直被session的delegate強(qiáng)引用著(上面有提到),這樣就造成了循環(huán)引用導(dǎo)致內(nèi)存泄漏。這是個(gè)坑啊??。這個(gè)問(wèn)題很早以前就有人在Github上提過(guò)了,@mattt當(dāng)時(shí)也回復(fù)了這里.
然后我看了AFNetworking 3.0 的示例Demo,發(fā)現(xiàn)它是這么用的,創(chuàng)建一個(gè)繼承AFHTTPSessionManager的類,提供獲取單例的方法:
+ (instancetype)sharedClient {
static AFAppDotNetAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[AFAppDotNetAPIClient alloc] initWithBaseURL:[NSURL URLWithString:AFAppDotNetAPIBaseURLString]];
_sharedClient.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
});
return _sharedClient;
}
那么以后再封裝AFNetworking 3.0 來(lái)用的話,得注意一下這個(gè)問(wèn)題咯。另外swift版本的 Alamofire 的實(shí)現(xiàn)是不一樣的,有點(diǎn)差別,(呵呵):
public static let sharedInstance: Manager = {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders
return Manager(configuration: configuration)
}()
最后
先寫到這里吧,有空再補(bǔ)充。以上均個(gè)人見(jiàn)解,歡迎交流。另外,個(gè)人簡(jiǎn)單封裝了一下AFNetworking 3.0以便快捷安全使用,地址如下:Githud.