NSURLSession
AFNHTTPSessionManager
AFHTTPSessionManager
is a subclass of AFURLSessionManager
with convenience methods for making HTTP requests. When a baseURL
is provided, requests made with the GET
/ POST
/ et al. convenience methods can be made with relative paths.
在.h文件中
遵守NSSecureCoding, NSCopying協(xié)議
定義屬性
baseURL(NSURL),requestSerializer(AFHTTPRequestSerializer)陕习,responseSerializer(AFHTTPResponseSerializer)碳想。
1.初始化方法:
+ (instancetype)manager;
- (instancetype)initWithBaseURL:(nullable NSURL *)url;
- (instancetype)initWithBaseURL:(nullable NSURL *)url sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
2.創(chuàng)建HTTP請(qǐng)求
主要是發(fā)送GET缩搅,POST(有無下載缺厉,downloadProgress是在Session queue中祭玉,不在主隊(duì)列)测暗,HEAD柜蜈,PUT,PATCH懊直,DELETE扒吁。
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id)parameters progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
只有POST請(qǐng)求有多種,需要加constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
將一個(gè)參數(shù)和拼接數(shù)據(jù)到HTTP主體室囊。該block參數(shù)是采用AFMultipartFormData
協(xié)議的對(duì)象雕崩。
在.m文件中
主要是方法:
- (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];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
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;
}
所有的網(wǎng)絡(luò)請(qǐng)求都調(diào)用dataTaskWithHTTPMethod這個(gè)方法來實(shí)現(xiàn)。
實(shí)現(xiàn)了:
- NSObject中的description方法來打印出BaseURL融撞,session盼铁,和operationQueue。
- NSSecureCoding中的supportsSecureCoding尝偎,initWithCoder饶火,encodeWithCoder方法。
- NSCopying中的copyWithZone方法(HTTPClient)
AFURLSessionManager
AFURLSessionManager
創(chuàng)建和管理一個(gè) NSURLSession
對(duì)象根據(jù)一個(gè)特殊的 NSURLSessionConfiguration
對(duì)象, 它符合<NSURLSessionTaskDelegate>
, <NSURLSessionDataDelegate>
, <NSURLSessionDownloadDelegate>
, and <NSURLSessionDelegate>
.
NSURLSession & NSURLSessionTask Delegate Methods:
NSURLSessionDelegate
- URLSession:didBecomeInvalidWithError:
- URLSession:didReceiveChallenge:completionHandler:
- URLSessionDidFinishEventsForBackgroundURLSession:
NSURLSessionTaskDelegate
-URLSession:willPerformHTTPRedirection:newRequest:completionHandler:
- URLSession:task:didReceiveChallenge:completionHandler:
-URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
- URLSession:task:needNewBodyStream:
- URLSession:task:didCompleteWithError:
NSURLSessionDataDelegate
URLSession:dataTask:didReceiveResponse:completionHandler:
URLSession:dataTask:didBecomeDownloadTask:
URLSession:dataTask:didReceiveData:
URLSession:dataTask:willCacheResponse:completionHandler:
NSURLSessionDownloadDelegate
-
URLSession:downloadTask:didFinishDownloadingToURL:
-URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:
URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:
在.h文件中
遵守協(xié)議protocol為 <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying>致扯,對(duì)應(yīng)相應(yīng)的代理方法肤寝。
定義屬性
1.session(NSURLSession),operationQueue(NSOperationQueue).responseSerializer(AFURLResponseSerialization)。
2.管理安全的策略:securityPolicy(AFSecurityPolicy).
3.管理檢測(cè)網(wǎng)絡(luò)的連通性:reachabilityManager(AFNetworkReachabilityManager).
4.獲得 Session Tasks:tasks抖僵,dataTasks,uploadTasks,downloadTasks.
5.管理回調(diào)的隊(duì)列:
completionQueue(dispatch_queue_t),completionGroup(dispatch_group_t).
定義方法:
- 初始化方法:
- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration;
- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks;
2.運(yùn)行Data Tasks:
- (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;
3.運(yùn)行上傳任務(wù):
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
4.運(yùn)行下載任務(wù):
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
還有就是把NSURLRequest替換成NSData,
5.獲取任務(wù)的進(jìn)程(Getting Progress for Tasks):
- (nullable NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task;
還有downloadProgressForTask
6.設(shè)置Session的代理回調(diào)
- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block;
- 設(shè)置Task的代理回調(diào)
- (void)setTaskNeedNewBodyStreamBlock:(nullable NSInputStream * (^)(NSURLSession *session, NSURLSessionTask *task))block;
- (void)setTaskWillPerformHTTPRedirectionBlock:(nullable NSURLRequest * (^)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request))block;
- (void)setTaskDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block;
- (void)setTaskDidSendBodyDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend))block;
- (void)setTaskDidCompleteBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSError * _Nullable error))block;
8.設(shè)置Data Task 的代理回調(diào)
- (void)setDataTaskDidReceiveResponseBlock:(nullable NSURLSessionResponseDisposition (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response))block;
- (void)setDataTaskDidBecomeDownloadTaskBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block;
- (void)setDataTaskDidReceiveDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block;
- (void)setDataTaskWillCacheResponseBlock:(nullable NSCachedURLResponse * (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse))block;
- (void)setDidFinishEventsForBackgroundURLSessionBlock:(nullable void (^)(NSURLSession *session))block;
9.設(shè)置Download Task的回調(diào)方法
- (void)setDownloadTaskDidFinishDownloadingBlock:(nullable NSURL * _Nullable (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block;
- (void)setDownloadTaskDidWriteDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block;
- (void)setDownloadTaskDidResumeBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes))block;
10.設(shè)置通知Notification
在.m 文件中
主要有是三個(gè)@interface和@implement鲤看。
一個(gè)是AFURLSessionManagerTaskDelegate。
一個(gè)是_AFURLSessionTaskSwizzling裆针。
一個(gè)是AFURLSessionManager刨摩。
- AFURLSessionManagerTaskDelegate
遵守協(xié)議<NSURLSessionTaskDelegate, NSURLSessionDataDelegate,NSURLSessionDownloadDelegate>寺晌,
NSProgress Tracking
- (void)setupProgressForTask:(NSURLSessionTask *)task
一部分是對(duì)代理持有的兩個(gè)屬性 uploadProgress和 downloadProgress設(shè)置回調(diào)
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
[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];
}];
}
設(shè)置上傳的取消暫停,下載一樣的道理澡刹。
另一個(gè)部分是對(duì) task 和 NSProgress的uploadProgress和downloadPreogress屬性進(jìn)行鍵值觀測(cè)呻征。
- (void)cleanUpProgressForTask:(NSURLSessionTask *)task
主要是移除download和upload的observer。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
主要是上面第一個(gè)函數(shù)中的實(shí)現(xiàn)NSURLSessionTask和NSURLSessionDownloadTask的類中下載和上傳的的接收和期望接收的字節(jié)數(shù)罢浇,發(fā)送和期望發(fā)送的字節(jié)數(shù)陆赋。改變進(jìn)度,并調(diào)用 block
NSURLSessionTaskDelegate
- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
如果遇到error嚷闭,dispatch_group_async攒岛,如果當(dāng)前 manager持有 completionGroup或者 completionQueue就在主線程中調(diào)用 completionHandler并發(fā)送通知(在主線程中)
如果在執(zhí)行當(dāng)前 task 時(shí)沒有遇到錯(cuò)誤,那么先對(duì)數(shù)據(jù)進(jìn)行序列化胞锰,然后同樣調(diào)用 block 并發(fā)送通知灾锯。
NSURLSessionDataTaskDelegate與NSURLSessionDownloadTaskDelegate
- (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
在收到數(shù)據(jù)后調(diào)用。并拼接data.
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
在下載完成對(duì)應(yīng)文件后調(diào)用嗅榕,并處理下載文件顺饮。如果fileManagerError,則發(fā)出通知凌那。
2._AFURLSessionTaskSwizzling
主要用到runtime的知識(shí)兼雄,來交換方法。修改 NSURLSessionTask的 resume和 suspend 方法帽蝶。這樣做的目的是為了在方法 resume或者 suspend被調(diào)用時(shí)發(fā)出通知赦肋。具體方法調(diào)劑過程是在 + load方法中進(jìn)行的.a.首先用 NSClassFromString(@"NSURLSessionTask")
判斷當(dāng)前部署的 iOS 版本是否含有類 NSURLSessionTask。b.因?yàn)?iOS7 和 iOS8 上對(duì)于 NSURLSessionTask的實(shí)現(xiàn)不同励稳,所以會(huì)通過 - [NSURLSession dataTaskWithURL:]方法返回一個(gè) NSURLSessionTask 實(shí)例佃乘。c .取得當(dāng)前類 _AFURLSessionTaskSwizzling 中的實(shí)現(xiàn) af_resume。d.如果當(dāng)前類 currentClass有 resume驹尼。
方法真:使用 swizzleResumeAndSuspendMethodForClass:調(diào)劑該類的 resume 和 suspend方法
假:currentClass = [currentClass superclass]
3.AFURLSessionManager(最重要的地方)
a.初始化在- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration
中恕稠,初始化會(huì)話配置,設(shè)置為默認(rèn)的defaultSessionConfiguration扶欣,初始化會(huì)話session,并設(shè)置會(huì)話的代理和代理隊(duì)列千扶,初始化響應(yīng)序列化responseSerializer料祠,安全認(rèn)證securityPolicy,和監(jiān)控網(wǎng)絡(luò)狀態(tài)reachabilityManager澎羞。
b. 兩通知:- (void)taskDidResume:(NSNotification *)notification
與- (void)taskDidSuspend:(NSNotification *)notification
c.然后為NSURLSessionTask設(shè)置AFURLSessionManagerTaskDelegate髓绽,
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task{
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = nil;
[self.lock lock];
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
[self.lock unlock];
return delegate;
} ```
AFURLSessionManager就是通過字典 mutableTaskDelegatesKeyedByTaskIdentifier來存儲(chǔ)并管理每一個(gè) NSURLSessionTask,它以 taskIdentifier為鍵存儲(chǔ) task妆绞。該方法使用 NSLock
來保證不同線程使用 mutableTaskDelegatesKeyedByTaskIdentifier時(shí)顺呕,不會(huì)出現(xiàn)**線程競(jìng)爭(zhēng)**的問題枫攀。
d.為DataTask加代理。
- (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```
為uploadTask加代理,為downloadTask加代理株茶。移除代理方法
- (void)removeDelegateForTask:(NSURLSessionTask *)task
.
用KVO檢測(cè)
- (NSArray *)tasksForKeyPath:(NSString *)keyPath
如果無效session来涨,則取消任務(wù):
- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks
設(shè)置response的序列
- (void)setResponseSerializer:(id <AFURLResponseSerialization>)responseSerializer
為任務(wù)添加(add)和移除(remove)通知觀察者.
- (void)addNotificationObserverForTask:(NSURLSessionTask *)task
管理NSURLSessionTask
先調(diào)用
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler```
方法傳入NSURLRequest,
-
(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;
}```
返回一個(gè) AFURLSessionManagerTaskDelegate對(duì)象启盛,將 completionHandler的
uploadProgressBlock和 downloadProgressBlock 傳入該對(duì)象并在相應(yīng)事件發(fā)生時(shí)進(jìn)行回調(diào)
然后
Reachability
AFNetworkReachabilityManager
主要是網(wǎng)絡(luò)的可用性蹦掐,類似于蘋果文檔的Reachability
AFNetworkReachabilityStatusUnknow= -1,
AFNetworkReachabilityStatusNotReachable = 0,
AFNetworkReachabilityStatusReachableViaWWAN = 1,
AFNetworkReachabilityStatusReachableViaWiFi = 2,
在.h文件中
先是初始化:
+ (instancetype)sharedManager;
+ (instancetype)manager;
+ (instancetype)managerForDomain:(NSString *)domain;
+ (instancetype)managerForAddress:(const void *)address;
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;
然后開始或停止可用性的監(jiān)控:
- (void)startMonitoring;
- (void)stopMonitoring;
- (NSString *)localizedNetworkReachabilityStatusString;
再設(shè)置網(wǎng)絡(luò)可用性來改變回調(diào):
- (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block;
最后是一些常量,通知和功能僵闯。
在.m文件中:
初始化:
+ (instancetype)sharedManager
中用dispatch_once卧抗,調(diào)用+ (instancetype)manager
,
+ (instancetype)managerForDomain:(NSString *)domain {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}
+ (instancetype)managerForAddress:(const void *)address {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}```
1. 使用 SCNetworkReachabilityCreateWithAddress或者 SCNetworkReachabilityCreateWithName生成一個(gè) SCNetworkReachabilityRef的引用。
2. 兩個(gè)方法會(huì)通過一個(gè)**域名**或者一個(gè) sockaddr_in的指針生個(gè) SCNetworkReachabilityRef鳖粟。
3. 調(diào)用 `- [AFNetworkReachabilityManager initWithReachability:] `將生成的 SCNetworkReachabilityRef引用傳給networkReachability.
4. 當(dāng)調(diào)用 CFBridgingRelease(reachability)后社裆,會(huì)把 reachability橋接成一個(gè) NSObject 對(duì)象賦值給self.networkReachability,然后釋放原來的 CoreFoundation 對(duì)象向图。
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
self = [super init];
if (!self) {
return nil;
}
_networkReachability = CFRetain(reachability);
self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;
return self;
}
監(jiān)控網(wǎng)絡(luò)狀態(tài):
- (void)startMonitoring {
[self stopMonitoring];
if (!self.networkReachability) {
return;
}
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
} - (void)stopMonitoring {
if (!self.networkReachability) {
return;
}
SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}
1. 先調(diào)用 - stopMonitoring方法泳秀,如果之前設(shè)置過對(duì)網(wǎng)絡(luò)狀態(tài)的監(jiān)聽,使用SCNetworkReachabilityUnscheduleFromRunLoop方法取消之前在 Main Runloop 中的監(jiān)聽.
2. 創(chuàng)建一個(gè)在每次網(wǎng)絡(luò)狀態(tài)改變時(shí)的回調(diào)block张漂,每次回調(diào)被調(diào)用時(shí)晶默,重新設(shè)置networkReachabilityStatus的屬性,調(diào)用networkReachabilityStatus航攒,
3. 創(chuàng)建一個(gè)SCNetworkReachabilityContext磺陡,其中的 callback就是上一步中的創(chuàng)建的 block 對(duì)象。這里的 AFNetworkReachabilityRetainCallback 和 AFNetworkReachabilityReleaseCallback
都是非常簡(jiǎn)單的 block漠畜,在回調(diào)被調(diào)用時(shí)币他,只是使用 Block_copy和 Block_release
這樣的宏。傳入的 info
會(huì)以參數(shù)的形式在 AFNetworkReachabilityCallback
執(zhí)行時(shí)傳入
static const void * AFNetworkReachabilityRetainCallback(const void *info) { return Block_copy(info); }
static void AFNetworkReachabilityReleaseCallback(const void *info) { if (info) { Block_release(info); } }
4. 當(dāng)目標(biāo)的網(wǎng)絡(luò)狀態(tài)改變時(shí)憔狞,會(huì)調(diào)用傳入的回調(diào)
5. 在 Main Runloop 中對(duì)應(yīng)的模式開始監(jiān)控網(wǎng)絡(luò)狀態(tài)蝴悉。
6. 取當(dāng)前的網(wǎng)絡(luò)狀態(tài),調(diào)用 callback
其中` SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);`回調(diào)了AFNetworkReachabilityManager的AFNetworkReachabilityCallback瘾敢。
總結(jié):
AFNetworkReachabilityManager實(shí)際上只是一個(gè)對(duì)底層 SystemConfiguration庫(kù)中的 C 函數(shù)封裝的類拍冠,它為我們隱藏了 C 語言的實(shí)現(xiàn),提供了統(tǒng)一的 Objective-C 語言接口簇抵。
它是 AFNetworking中一個(gè)即插即用的模塊
# Security
## AFSecurityPolicy
為了阻止中間人攻擊庆杜,以及其它漏洞的工具。
用枚舉設(shè)置了驗(yàn)證服務(wù)器被信任的方式SSL的類型有None碟摆,PublicKey晃财,還有Certificate。
AFSSLPinningModeNone是默認(rèn)的認(rèn)證方式典蜕,只會(huì)在系統(tǒng)的信任的證書列表中對(duì)服務(wù)端返回的證書進(jìn)行驗(yàn)證
AFSSLPinningModeCertificate需要客戶端預(yù)先保存服務(wù)端的證書
AFSSLPinningModePublicKey也需要預(yù)先保存服務(wù)端發(fā)送的證書断盛,但是這里只會(huì)驗(yàn)證證書中的公鑰是否正確
遵守<NSSecureCoding, NSCopying>罗洗。
### 從bundle獲取證書,并返回
`+ (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle;`
### 獲取特殊的安全策略
`+ (instancetype)defaultPolicy;`
### 初始化
`+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;`
`+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;`
初始化方法時(shí),主要目的是設(shè)置**驗(yàn)證服務(wù)器是否受信任的方式**钢猛。
### 驗(yàn)證服務(wù)端是否信任
-
(BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);if (!AFServerTrustIsValid(serverTrust)) { return NO; } // obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA) NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust); for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) { if ([self.pinnedCertificates containsObject:trustChainCertificate]) { return YES; } } return NO; } case AFSSLPinningModePublicKey: { NSUInteger trustedPublicKeyCount = 0; NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust); for (id trustChainPublicKey in publicKeys) { for (id pinnedPublicKey in self.pinnedPublicKeys) { if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) { trustedPublicKeyCount += 1; } } } return trustedPublicKeyCount > 0; }
}
return NO;
}```
- 所以如果沒有提供證書或者不驗(yàn)證證書伙菜,并且還設(shè)置 allowInvalidCertificates為真,滿足上面的所有條件厢洞,說明這次的驗(yàn)證是不安全的仇让,會(huì)直接返回 NO.
- 設(shè)置 policy:如果要驗(yàn)證域名的話,就以域名為參數(shù)創(chuàng)建一個(gè) SecPolicyRef躺翻,否則會(huì)創(chuàng)建一個(gè)符合 X509 標(biāo)準(zhǔn)的默認(rèn)SecPolicyRef對(duì)象
- 驗(yàn)證證書的有效性:如果只根據(jù)信任列表中的證書進(jìn)行驗(yàn)證丧叽,即 self.SSLPinningMode == AFSSLPinningModeNone
。如果允許無效的證書的就會(huì)直接返回 YES
公你。不允許就會(huì)對(duì)服務(wù)端信任進(jìn)行驗(yàn)證踊淳。
如果服務(wù)器信任無效,并且不允許無效證書陕靠,就會(huì)返回 NO. - 根據(jù) SSLPinningMode對(duì)服務(wù)器信任進(jìn)行驗(yàn)證
AFSSLPinningModeNone直接返回 NO
AFSSLPinningModeCertificate
AFSSLPinningModePublicKey
Serialization
們對(duì)發(fā)出請(qǐng)求以及接收響應(yīng)的過程進(jìn)行序列化迂尝,這涉及到兩個(gè)模塊:
AFURLRequestSerialization
修改請(qǐng)求(主要是 HTTP 請(qǐng)求)的頭部,提供了一些語義明確的接口設(shè)置 HTTP 頭部字段剪芥。
主要用于 AFHTTPSessionManager中垄开,因?yàn)樗饕糜?strong>修改 HTTP 頭部。
AFURLResponseSerialization(重要)
處理響應(yīng)的模塊税肪,將請(qǐng)求返回的數(shù)據(jù)解析成對(duì)應(yīng)的格式.這個(gè)模塊使用在 AFURLSessionManager
也就是核心類溉躲。其實(shí)也只是個(gè)協(xié)議
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
遵循這個(gè)協(xié)議的類同時(shí)也要遵循 NSObject、NSSecureCoding 和 NSCopying 這三個(gè)協(xié)議益兄,實(shí)現(xiàn)安全編碼锻梳、拷貝以及 Objective-C 對(duì)象的基本行為。
這個(gè)協(xié)議只有一個(gè)必須實(shí)現(xiàn)的方法:
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;```
在.h文件中中有7個(gè)類:
第一個(gè)
`@interface AFHTTPResponseSerializer : NSObject <AFURLResponseSerialization>`剩下的六個(gè)AFJSONResponseSerializer净捅,AFXMLParserResponseSerializer疑枯,AFXMLDocumentResponseSerializer,AFPropertyListResponseSerializer蛔六,AFImageResponseSerializer荆永,AFCompoundResponseSerializer都繼承自AFHTTPResponseSerializer。
### AFHTTPResponseSerializer
先看一下AFHTTPResponseSerializer的實(shí)現(xiàn):
- (instancetype)serializer {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;
self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
self.acceptableContentTypes = nil;
return self;
}
因?yàn)槭菍?duì) HTTP 響應(yīng)進(jìn)行序列化国章,所以這里設(shè)置了 stringEncoding為 NSUTF8StringEncoding而且沒有對(duì)接收的內(nèi)容類型加以限制屁魏。
將 acceptableStatusCodes設(shè)置為從 200 到 299 之間的狀態(tài)碼, 因?yàn)橹挥羞@些狀態(tài)碼表示**獲得了有效的響應(yīng)**
驗(yàn)證響應(yīng)的有效性:
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
BOOL responseIsValid = YES;
NSError *validationError = nil;
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
!([response MIMEType] == nil && [data length] == 0)) {
if ([data length] > 0 && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
NSURLErrorFailingURLErrorKey:[response URL], AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
}
responseIsValid = NO;
}
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
responseIsValid = NO;
}
}
if (error && !responseIsValid) {
*error = validationError;
}
return responseIsValid;
}```
這個(gè)方法根據(jù)在初始化方法中初始化的屬性 acceptableContentTypes 和 acceptableStatusCodes來判斷當(dāng)前響應(yīng)是否有效。
AFURLResponseSerialization協(xié)議的實(shí)現(xiàn)
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error{
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}```
主要是調(diào)用上面的方法對(duì)響應(yīng)進(jìn)行驗(yàn)證捉腥,然后返回?cái)?shù)據(jù),實(shí)在是沒什么難度你画。
1. NSSecureCoding
對(duì)安全協(xié)議的實(shí)現(xiàn)抵碟,就是支持安全編碼桃漾,根據(jù)屬性acceptableStatusCodes,acceptableContentTypes然后初始化拟逮,然后encode
2. NSCopying
實(shí)現(xiàn)copy屬性
### AFJSONResponseSerializer
初始化方法只是在調(diào)用父類的初始化方法之后更新了 acceptableContentTypes
屬性:
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
return self;
}```
這個(gè)類中與父類差別最大的就是對(duì) AFURLResponseSerialization協(xié)議的實(shí)現(xiàn)撬统。
- (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(
NSError *__autoreleasing *)error
{
#1: 驗(yàn)證請(qǐng)求
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
#2: 解決一個(gè)由只包含一個(gè)空格的響應(yīng)引起的 bug, 略
#3: 序列化 JSON
id responseObject = nil;
NSError *serializationError = nil;
// Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
// See https://github.com/rails/rails/issues/1742
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
if (data.length > 0 && !isSpace) {
responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
} else {
return nil;
}
if (self.removesKeysWithNullValues && responseObject) {
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
#4: 移除 JSON 中的 null
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error); }
return responseObject;
}```
然后實(shí)現(xiàn)NSSecureCoding,NSCopying與上面類似
其他幾個(gè)子類實(shí)現(xiàn)差不多敦迄。
## AFURLRequestSerialization
AFURLRequestSerialization的主要工作是對(duì)發(fā)出的 HTTP 請(qǐng)求進(jìn)行處理恋追,它有幾部分的工作需要完成。
而這個(gè)文件中的大部分類都是為 AFHTTPRequestSerializer服務(wù)的:
1. 處理查詢的 URL 參數(shù)
2. 設(shè)置 HTTP 頭部字段
3. 設(shè)置請(qǐng)求的屬性
4. 分塊上傳
### 處理查詢參數(shù)
處理查詢參數(shù)這部分主要是通過 AFQueryStringPair還有一些 C 函數(shù)來完成的罚屋,這個(gè)類有兩個(gè)屬性 field和 value對(duì)應(yīng) HTTP 請(qǐng)求的查詢 URL 中的參數(shù)苦囱。在.m文件中,先是它的實(shí)現(xiàn)脾猛。
@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (instancetype)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end
其中URLEncodedStringValue方法會(huì)返回
`return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];`
key=value這種格式撕彤,同時(shí)使用 AFPercentEscapedStringFromString函數(shù)來對(duì) field和 value進(jìn)行處理,將其中的 :#[]@!$&'()*+,;=等字符轉(zhuǎn)換為百分號(hào)表示的形式猛拴。
這一部分代碼還負(fù)責(zé)返回查詢參數(shù)羹铅,將 AFQueryStringPair中key和value
轉(zhuǎn)換為以下這種形式:
username=tom&password=123456&hello[world]=helloworld
下面是用FOUNDATION_EXPORT定義的NSArray的數(shù)組,有AFQueryStringPairsFromDictionary愉昆,AFQueryStringPairsFromKeyAndValue职员,AFQueryStringFromParameters,AFQueryStringPairsFromDictionary跛溉,AFQueryStringPairsFromKeyAndValue焊切。
它的實(shí)現(xiàn)主要依賴于一個(gè)遞歸函數(shù) AFQueryStringPairsFromKeyAndValue,如果當(dāng)前的 value
是一個(gè)集合類型的話倒谷,那么它就會(huì)不斷地遞歸調(diào)用自己蛛蒙。
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
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;
}
返回一個(gè)數(shù)組
[ username=tom, password=123456, hello[world]=helloworld]
得到這個(gè)數(shù)組之后就會(huì)調(diào)用 AFQueryStringFromParameters使用 &來拼接它們。
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}```
- 設(shè)置請(qǐng)求的屬性
這個(gè)下面有一個(gè)AFStreamingMultipartFormData的聲明渤愁,是多形式的數(shù)據(jù)流牵祟。
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;
}```
在這些屬性被設(shè)置時(shí),會(huì)觸發(fā) KVO抖格,然后將新的屬性存儲(chǔ)在一個(gè)名為 mutableObservedChangedKeyPaths的字典中:
- (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];
}
}
}```
然后會(huì)在生成NSURLRequest的時(shí)候設(shè)置這些屬性:
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
- 設(shè)置HTTP頭部字段
在AFHTTPRequestSerializer的interface文件中提供了一些屬性方便我們?cè)O(shè)置 HTTP 頭部字段
mutableHTTPRequestHeaders.
- (NSDictionary *)HTTPRequestHeaders {
//在設(shè)置 HTTP 頭部字段時(shí)诺苹,都會(huì)存儲(chǔ)到這個(gè)可變字典中。而當(dāng)真正使用時(shí)雹拄,用這個(gè)方法收奔,來獲取對(duì)應(yīng)版本的不可變字典。
return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
}
- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
return [self.mutableHTTPRequestHeaders valueForKey:field];
}
這個(gè)類是如何設(shè)置一些我們平時(shí)常用的頭部字段的滓玖。首先是 User-Agent坪哄,AFHTTPRequestSerializer剛剛初始化時(shí),就會(huì)根據(jù)當(dāng)前編譯的平臺(tái)生成一個(gè) userAgent。在iOS翩肌,ios_watch還有iOS_VERSION_MIN的平臺(tái)下生成不同模暗。
之后有方法設(shè)置授權(quán)頭部用用戶名和密碼,還有清除方法:
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password
{
NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
[self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
}
- (void)clearAuthorizationHeader {
[self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
}```
設(shè)置了AFHTTPBodyPart念祭,AFMultipartBodyStream的@interface兑宇。
字符串:
#UIKit+AFNetworking
參考:
[源碼解析](https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/AFNetworking/%E5%A4%84%E7%90%86%E8%AF%B7%E6%B1%82%E5%92%8C%E5%93%8D%E5%BA%94%20AFURLSerialization%EF%BC%88%E4%B8%89%EF%BC%89.md)