AFNetworking的源碼解讀馬上就結束了,這一篇應該算是倒數第二篇,下一篇會是對AFNetworking中的技術點進行總結。
前言
上一篇我們總結了 UIActivityIndicatorView UIRefreshControl UIImageView 這3個控件的分類。那么這一篇就總結下剩余的3個分類:UIButton UIProgressView UIWebView 。
UIButton+AFNetworking
UIButton跟圖片相關的屬性大概有兩個,Image
和BackgroundImage
.所以這個分類就是賦予他們異步加載圖片的能力。
其中核心方法為:
示例代碼:
static char AFImageDownloadReceiptNormal;
static char AFImageDownloadReceiptHighlighted;
static char AFImageDownloadReceiptSelected;
static char AFImageDownloadReceiptDisabled;
static const char * af_imageDownloadReceiptKeyForState(UIControlState state) {
switch (state) {
case UIControlStateHighlighted:
return &AFImageDownloadReceiptHighlighted;
case UIControlStateSelected:
return &AFImageDownloadReceiptSelected;
case UIControlStateDisabled:
return &AFImageDownloadReceiptDisabled;
case UIControlStateNormal:
default:
return &AFImageDownloadReceiptNormal;
}
}
- (AFImageDownloadReceipt *)af_imageDownloadReceiptForState:(UIControlState)state {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, af_imageDownloadReceiptKeyForState(state));
}
- (void)af_setImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt
forState:(UIControlState)state
{
objc_setAssociatedObject(self, af_imageDownloadReceiptKeyForState(state), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
我們分析下上邊的代碼。我們都知道UIButton有4種狀態。這個分類能夠支持不同的狀態加載不同的圖片。同樣我們也知道,每一個圖片的加載,都需要一個AFImageDownloadReceipt憑證。所以,我們要為UIButton擴展一個根據狀態獲取憑證的方法,就是:af_imageDownloadReceiptForState:
。既然有獲取憑證的方法,就應該有根據狀態設置憑證的方法,那就是:af_setImageDownloadReceipt: forState:
.
上邊的af_imageDownloadReceiptKeyForState
方法的作用就是為運行時提供一個key,這個key是一個內存地址。也可使用@Selector()。同理,下邊的代碼擴展了BackgroundImage,原理同上,就不做解釋了
示例代碼:
static char AFBackgroundImageDownloadReceiptNormal;
static char AFBackgroundImageDownloadReceiptHighlighted;
static char AFBackgroundImageDownloadReceiptSelected;
static char AFBackgroundImageDownloadReceiptDisabled;
static const char * af_backgroundImageDownloadReceiptKeyForState(UIControlState state) {
switch (state) {
case UIControlStateHighlighted:
return &AFBackgroundImageDownloadReceiptHighlighted;
case UIControlStateSelected:
return &AFBackgroundImageDownloadReceiptSelected;
case UIControlStateDisabled:
return &AFBackgroundImageDownloadReceiptDisabled;
case UIControlStateNormal:
default:
return &AFBackgroundImageDownloadReceiptNormal;
}
}
- (AFImageDownloadReceipt *)af_backgroundImageDownloadReceiptForState:(UIControlState)state {
return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, af_backgroundImageDownloadReceiptKeyForState(state));
}
- (void)af_setBackgroundImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt
forState:(UIControlState)state
{
objc_setAssociatedObject(self, af_backgroundImageDownloadReceiptKeyForState(state), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
示例代碼:
// 使用運行時設置sharedImageDownloader
+ (AFImageDownloader *)sharedImageDownloader {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
return objc_getAssociatedObject(self, @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance];
#pragma clang diagnostic pop
}
// 使用運行時設置setSharedImageDownloader:
+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader {
objc_setAssociatedObject(self, @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
如果對這個分類的核心方法感興趣的話,可以參考上一篇。里邊有詳細的解釋,原理和代碼非常非常像,在這里為了節省篇幅就不做多余的說明了。在這個分類中下邊圖片的那行代碼可以注釋掉。
UIProgressView+AFNetworking
UIProgressView的這個分類,實現原理就是監聽NSURLSessionUploadTask
或者NSURLSessionDownloadTask
中的"state" "countOfBytesSent" "countOfBytesReceived" 。然后設置進度就可以了。
示例代碼:
- (BOOL)af_uploadProgressAnimated {
return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_uploadProgressAnimated)) boolValue];
}
- (void)af_setUploadProgressAnimated:(BOOL)animated {
objc_setAssociatedObject(self, @selector(af_uploadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)af_downloadProgressAnimated {
return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_downloadProgressAnimated)) boolValue];
}
- (void)af_setDownloadProgressAnimated:(BOOL)animated {
objc_setAssociatedObject(self, @selector(af_downloadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
看到上邊的這四個方法,我突然間明白,假如我們需要一個屬性記錄某一個狀態的話,通常我們會寫一個屬性,但是看上邊的代碼,是通過擴展了幾個方法來達到記錄狀態的目的。這就說明同樣一個結果,可以有不同的實現手段。但我不太明白這兩個的區別是什么?
示例代碼:
- (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task
animated:(BOOL)animated
{
[task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];
[task addObserver:self forKeyPath:@"countOfBytesSent" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];
[self af_setUploadProgressAnimated:animated];
}
- (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task
animated:(BOOL)animated
{
[task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];
[task addObserver:self forKeyPath:@"countOfBytesReceived" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];
[self af_setDownloadProgressAnimated:animated];
}
示例代碼:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(__unused NSDictionary *)change
context:(void *)context
{
// 判斷是不是我們需要的監聽對象
if (context == AFTaskCountOfBytesSentContext || context == AFTaskCountOfBytesReceivedContext) {
// 上傳
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
if ([object countOfBytesExpectedToSend] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setProgress:[object countOfBytesSent] / ([object countOfBytesExpectedToSend] * 1.0f) animated:self.af_uploadProgressAnimated];
});
}
}
// 下載
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
if ([object countOfBytesExpectedToReceive] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setProgress:[object countOfBytesReceived] / ([object countOfBytesExpectedToReceive] * 1.0f) animated:self.af_downloadProgressAnimated];
});
}
}
// 狀態
if ([keyPath isEqualToString:NSStringFromSelector(@selector(state))]) {
if ([(NSURLSessionTask *)object state] == NSURLSessionTaskStateCompleted) {
@try {
// 移除state
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(state))];
// 移除countOfBytesSent
if (context == AFTaskCountOfBytesSentContext) {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
}
// 移除countOfBytesReceived
if (context == AFTaskCountOfBytesReceivedContext) {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
}
}
@catch (NSException * __unused exception) {}
}
}
}
}
UIWebView+AFNetworking
UIWebView的這個分類是這幾個分類中最讓我驚訝的一個。讓我真正認識到條條大路通羅馬到底是什么意思。有時候人的思想確實會被固有的思維所束縛。**這里只是用了UIWebView的loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString )textEncodingName baseURL:(NSURL )baseURL方法
你會發現使用這個分類配合UIWebView,所有的事情都變得很簡單。
示例代碼:
@interface UIWebView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setURLSessionTask:) NSURLSessionDataTask *af_URLSessionTask;
@end
@implementation UIWebView (_AFNetworking)
- (NSURLSessionDataTask *)af_URLSessionTask {
return (NSURLSessionDataTask *)objc_getAssociatedObject(self, @selector(af_URLSessionTask));
}
- (void)af_setURLSessionTask:(NSURLSessionDataTask *)af_URLSessionTask {
objc_setAssociatedObject(self, @selector(af_URLSessionTask), af_URLSessionTask, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
為UIWebView擴展了一個私有屬性af_URLSessionTask,定義為每一次請求,就會對應一個af_URLSessionTask。
示例代碼:
- (AFHTTPSessionManager *)sessionManager {
static AFHTTPSessionManager *_af_defaultHTTPSessionManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_af_defaultHTTPSessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
_af_defaultHTTPSessionManager.requestSerializer = [AFHTTPRequestSerializer serializer];
_af_defaultHTTPSessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
});
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
return objc_getAssociatedObject(self, @selector(sessionManager)) ?: _af_defaultHTTPSessionManager;
#pragma clang diagnostic pop
}
- (void)setSessionManager:(AFHTTPSessionManager *)sessionManager {
objc_setAssociatedObject(self, @selector(sessionManager), sessionManager, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
為UIWebView擴展的一個sessionManager屬性。實現了setter和getter方法。這樣在后邊直接使用self.sessionManager
就可以,不用創建了。
示例代碼:
- (void)loadRequest:(NSURLRequest *)request
MIMEType:(NSString *)MIMEType
textEncodingName:(NSString *)textEncodingName
progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
success:(NSData * (^)(NSHTTPURLResponse *response, NSData *data))success
failure:(void (^)(NSError *error))failure
{
// 檢查參數
NSParameterAssert(request);
// 如果正處於運行或者暫停裝狀態,就取消之前的任務task並設置為nil
if (self.af_URLSessionTask.state == NSURLSessionTaskStateRunning || self.af_URLSessionTask.state == NSURLSessionTaskStateSuspended) {
[self.af_URLSessionTask cancel];
}
self.af_URLSessionTask = nil;
__weak __typeof(self)weakSelf = self;
NSURLSessionDataTask *dataTask;
dataTask = [self.sessionManager
GET:request.URL.absoluteString
parameters:nil
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
// 請求成功后,調用success block
if (success) {
success((NSHTTPURLResponse *)task.response, responseObject);
}
// 顯示數據
[strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[task.currentRequest URL]];
// 調用webViewDidFinishLoad
if ([strongSelf.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
[strongSelf.delegate webViewDidFinishLoad:strongSelf];
}
}
failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
if (failure) {
failure(error);
}
}];
self.af_URLSessionTask = dataTask;
// 設置progress,這個來自於self.sessionManager
if (progress != nil) {
*progress = [self.sessionManager downloadProgressForTask:dataTask];
}
// 開啟任務
[self.af_URLSessionTask resume];
// 調用webViewDidStartLoad方法
if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
[self.delegate webViewDidStartLoad:self];
}
}
--
- (void)loadRequest:(NSURLRequest *)request
progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
success:(NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success
failure:(void (^)(NSError *error))failure
{
[self loadRequest:request MIMEType:nil textEncodingName:nil progress:progress success:^NSData *(NSHTTPURLResponse *response, NSData *data) {
NSStringEncoding stringEncoding = NSUTF8StringEncoding;
if (response.textEncodingName) {
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (encoding != kCFStringEncodingInvalidId) {
stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding);
}
}
NSString *string = [[NSString alloc] initWithData:data encoding:stringEncoding];
if (success) {
string = success(response, string);
}
return [string dataUsingEncoding:stringEncoding];
} failure:failure];
}
總結
就一句話,UIWebView+AFNetworking模擬了UIWebView加載數據的過程。模擬,模擬,模擬。。。
推薦閱讀
AFNetworking 3.0 源碼解讀(一)之 AFNetworkReachabilityManager
AFNetworking 3.0 源碼解讀(二)之 AFSecurityPolicy
AFNetworking 3.0 源碼解讀(三)之 AFURLRequestSerialization
AFNetworking 3.0 源碼解讀(四)之 AFURLResponseSerialization
AFNetworking 3.0 源碼解讀(五)之 AFURLSessionManager
AFNetworking 3.0 源碼解讀(六)之 AFHTTPSessionManager
AFNetworking 3.0 源碼解讀(七)之 AFAutoPurgingImageCache
AFNetworking 3.0 源碼解讀(八)之 AFImageDownloader
AFNetworking 3.0 源碼解讀(九)之 AFNetworkActivityIndicatorManager
AFNetworking 3.0 源碼解讀(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking