首先來介紹下AFNetWorking,官方介紹如下:
AFNetworking is a delightful networking library for iOS and Mac OS X. It’s built on top of theFoundation URL Loading System, extending the powerful high-level networking abstractions built into Cocoa. It has a modular architecture with well-designed, feature-rich APIs that are a joy to use.
Perhaps the most important feature of all, however, is the amazing community of developers who use and contribute to AFNetworking every day. AFNetworking powers some of the most popular and critically-acclaimed apps on the iPhone, iPad, and Mac.
Choose AFNetworking for your next project, or migrate over your existing projects—you’ll be happy you did!
翻譯過來簡單來說就是
AFNetworking是一個適用于iOS和Mac OS X兩個平臺的網(wǎng)絡(luò)庫,它是基于Foundation URL Loading System上進(jìn)行了一套封裝,并且提供了豐富且優(yōu)美的API接口給使用者使用
相信從star數(shù)和fork數(shù)來看,大家都能明白這個庫是多么的受歡迎了,所以了解這個庫對于一個iOS開發(fā)來說是極為重要的!
這個是AFNetworking的github地址:
GitHub –https://github.com/AFNetworking/AFNetworking
在使用前閱讀README是非常重要的,里面往往包括了這個庫的介紹、安裝和使用等等,對于快速了解一個庫來說,這是非常有幫助的
首先我們在AFNetWorking源碼地址里download下來,打開工程文件,可以看到里面內(nèi)容分為兩個部分,
一個是AFNetworking,另一個是UIKit+AFNetworking
很明顯,第一個是用來做網(wǎng)絡(luò)請求相關(guān)的,第二個則是和UI使用相關(guān)的,我們先看第一個在看完頭文件和README之后,你會發(fā)現(xiàn)AFURLSessionManager和AFHTTPSessionManager是里面比較重要的兩個類這里我先講AFURLSessionManager這個類首先瀏覽完這個類從API,發(fā)現(xiàn)其主要提供了數(shù)據(jù)的請求、上傳和下載功能
在屬性方面:
@property(readonly,nonatomic,strong)NSArray *tasks;
@property(readonly,nonatomic,strong)NSArray *dataTasks;
@property(readonly,nonatomic,strong)NSArray *uploadTasks;
@property(readonly,nonatomic,strong)NSArray *downloadTasks;
通過這四個屬性,我們分別可以拿到總的任務(wù)集合、數(shù)據(jù)任務(wù)集合、上傳任務(wù)集合和下載任務(wù)集合
<p>
@property(nonatomic,assign)BOOL attemptsToRecreateUploadTasksForBackgroundSessions;
這個屬性非常重要,注釋里面寫到,在iOS7中存在一個bug,在創(chuàng)建后臺上傳任務(wù)時,有時候會返回nil,所以為了解決這個問題,AFNetworking遵照了蘋果的建議,在創(chuàng)建失敗的時候,會重新嘗試創(chuàng)建,次數(shù)默認(rèn)為3次,所以你的應(yīng)用如果有場景會有在后臺上傳的情況的話,記得將該值設(shè)為YES,避免出現(xiàn)上傳失敗的問題
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidResumeNotification;
在對外提供的notification key里面,使用了FOUNDATION_EXPORT來定義常量,使用FOUNDATION_EXPORT和extern或者define有什么區(qū)別呢?
FOUNDATION_EXPORT在c文件編譯下是和extern等同,在c++文件編譯下是和extern “C”等同,在32位機(jī)的環(huán)境下又是另外編譯情況,在兼容性方面,F(xiàn)OUNDATION_EXPORT做的會更好。
這里還提到了效率方面的問題:iOS開發(fā)的一些技巧
"http://www.itdecent.cn/p/f547eb0368c4"
進(jìn)入到實(shí)現(xiàn)文件里面,我們可以看到在外部API調(diào)用dataTask、uploadTask、downloadTask方法實(shí)際上都是completionHanlder block返回出來的,但是我們知道網(wǎng)絡(luò)請求是delegate返回結(jié)果的,AF內(nèi)部做了巧妙的操作,他對每個task都增加代理設(shè)置
- (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];
});
// 每個task里面都會調(diào)用addDelegate方法
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
在設(shè)置里面,每個task會在內(nèi)部創(chuàng)建AFURLSessionManagerTaskDelegate對象,并設(shè)置completionHandler、uploadProgressBlock、downloadProgressBlock回調(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
{
// 初始化delegate對象
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
delegate.manager = self;
// 將task的completionHandler賦給delegate,系統(tǒng)網(wǎng)絡(luò)請求delegate 調(diào)用該block,返回結(jié)果
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
// 對task進(jìn)行delegate
[self setDelegate:delegate forTask:dataTask];
// 設(shè)置上傳和下載進(jìn)度回調(diào)
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
然后delegate對象利用kvo將task對一些方法進(jìn)行監(jiān)聽,并且監(jiān)聽到變化時,通過block返回,將delegate轉(zhuǎn)成block出去
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
// 斷言
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
// task使用kvo對一些方法監(jiān)聽,返回上傳或者下載的進(jìn)度
[delegate setupProgressForTask:task];
// sessionManager對暫停task和恢復(fù)task進(jìn)行注冊通知
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
在原先IM的設(shè)計時,因?yàn)榻涌诘臄?shù)量并不多,所以在AsyncSocket的delegate回調(diào)后,我們依舊是采用delegate回調(diào)給業(yè)務(wù)層,但是隨著接口數(shù)量的增加,業(yè)務(wù)層對于回調(diào)的處理更加困難和不可控,在重構(gòu)IM的時候,我們也參考學(xué)習(xí)了AF的做法,我們通過對唯一標(biāo)識和每個請求做一一綁定,將請求的上下文關(guān)聯(lián)起來,這樣讓socket長連接的請求的也想http請求一樣,都由block回去,對于業(yè)務(wù)層的處理也方便更多
setupProgressForTask方法主要是對task和progress設(shè)置監(jiān)聽
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
// 設(shè)置上傳和下載的新值
}
else if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}
在第一個if判斷里面,object判斷是否是NSURLSessionTask類或者是否是NSURLSessionDownloadTask類,但是進(jìn)到NSURLSessionDownloadTask的時候,我們可以看到NSURLSessionDownloadTask是NSURLSessionTask的子類,那為什么還要判斷這個呢?
NSURLSessionTask實(shí)際上是Class cluster,通過NSURLSession生成的task返回的并不一定是指定的task類型。因此kindOfClass并不總會生效,具體可以參見AFURLSessionManager.m在load方法中的說明。
特定于當(dāng)前問題,是由于iOS 7上NSCFURLSessionDownloadTask的基類并不是NSCFURLSessionTask,因此isKindOfClass會出錯。查看對應(yīng)的commit就可以知道了。
在NSURLSessionTaskDelegate的代理里面,只是做了兩件事情,第一個是獲取數(shù)據(jù),將responseSerializer和downloadFileURL或data存到userInfo里面,第二個是根據(jù)error是否為空值,做下一步處理
#pragma mark - NSURLSessionTaskDelegate
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
// 獲取數(shù)據(jù),將responseSerializer和downloadFileURL或data存到userInfo里面
__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 (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
if (error) {
// 有error時處理
} else {
// 無error時正常處理
}
#pragma clang diagnostic pop
}
在有error時,userInfo先存儲error,然后檢查manager是否有completionGroup和completionQueue,沒有的話,就創(chuàng)建一個dispatch_group_t和在主線程上做completionHandler的操作,并在主線程中發(fā)送一個AFNetworkingTaskDidCompleteNotification通知,這個通知在UIKit+AFNetworking里UIRefreshControl +AFNetworking里也會接收到,用來停止刷新,如果你不使用AF的UI部分,你可以通過接收這個通知來做操作
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
在沒有error時,會先對數(shù)據(jù)進(jìn)行一次序列化操作,然后下面的處理就和有error的那部分一樣了
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];
});
});
});
一開始我們就看到了clang命令,這個的作用是用來消除特定區(qū)域的clang的編譯警告,-Wgnu則是消除?:警告,這個是clang的警告message列表Which Clang Warning Is Generating This Message?
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
// some codes
#pragma clang diagnostic pop
}
再下面兩個則是收到數(shù)據(jù)和下載文件的回調(diào)處理
#pragma mark - NSURLSessionDataTaskDelegate
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
[self.mutableData appendData:data];
}
#pragma mark - NSURLSessionDownloadTaskDelegate
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
NSError *fileManagerError = nil;
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
[[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];
if (fileManagerError) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
在剛才說到的load方法里面,對系統(tǒng)的resume和suspend方法進(jìn)行了替換
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
}
if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
}
}
替換之后,只是增加了通知處理而已
- (void)af_resume {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_resume];
if (state != NSURLSessionTaskStateRunning) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
}
}
- (void)af_suspend {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_suspend];
if (state != NSURLSessionTaskStateSuspended) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
}
}
在調(diào)用替換和增加方法時候,用到了關(guān)鍵字inline,inline是為了防止反匯編之后,在符號表里面看不到你所調(diào)用的該方法,否則別人可以通過篡改你的返回值來造成攻擊,iOS安全–使用static inline方式編譯函數(shù),防止靜態(tài)分析,特別是在使用swizzling的時候,那除了使用swizzling動態(tài)替換函數(shù)方法之外,還有別的方法么?有,修改IMP指針指向的方法,輕松學(xué)習(xí)之 IMP指針的作用 – CocoaChina_讓移動開發(fā)更簡單
static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));
}
在+ load方法中,我們又看到了GCC命令,那clang和GCC在使用的時機(jī)有沒有什么區(qū)別?通常情況下,在GCC特有的處理或者是在GCC,clang和其他兼容GCC的編譯器時,盡量使用#pragma GCC,clang特有的處理時,使用#pragma clang,這個是GCC的message表
+ (void)load {
// ...
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
// ...
}
看完之后,有個疑問,查了資料也沒有找到:
在NSURLSessionDelegate的URLSession:didReceiveChallenge:completionHandler:方法里面disposition會對credential對象做非空判斷然后再賦值校驗(yàn)類型,但是NSURLSessionTaskDelegate的
– [URLSession:task:didReceiveChallenge:completionHandler:]方法里面disposition并不對credential對象做判斷,而是直接就賦值校驗(yàn)類型,有知道的,歡迎留言交流