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加载数据的过程。模拟,模拟,模拟。。。