博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking
阅读量:4958 次
发布时间:2019-06-12

本文共 12852 字,大约阅读时间需要 42 分钟。

AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结。

前言

我们总结了 UIActivityIndicatorView UIRefreshControl UIImageView 这3个控件的分类。那么这一篇就总结下剩余的3个分类:UIButton UIProgressView UIWebView

UIButton+AFNetworking

UIButton跟图片相关的属性大概有两个,ImageBackgroundImage.所以这个分类就是赋予他们异步加载图片的能力。

其中核心方法为:

637318-20160830101357183-1012134374.png

637318-20160830101405433-860115248.png

637318-20160830101413574-1894937106.png

示例代码:

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);}

如果对这个分类的核心方法感兴趣的话,可以参考。里边有详细的解释,原理和代码非常非常像,在这里为了节省篇幅就不做多余的说明了。在这个分类中下边图片的那行代码可以注释掉。

637318-20160830104605058-347253971.png

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

推荐阅读

转载于:https://www.cnblogs.com/machao/p/5821462.html

你可能感兴趣的文章
JavaScript 图表库 xCharts
查看>>
随笔 javascript-抽象工厂模式
查看>>
机器学习----人脸对齐的算法-ASM.AAM..CLM.SDM
查看>>
Android项目的目录结构
查看>>
spring-cloud服务器雪崩效应
查看>>
C++中“引用”的底层实现
查看>>
ZOJ 1602. Multiplication Puzzle (DP)
查看>>
Spring Cloud分布式微服务云架构集成项目
查看>>
【Android学习专题】控件组件篇:Dialog汇总
查看>>
Dynamic Signals and Slots
查看>>
jquery datatable 参数
查看>>
Leetcode-888 两句话中的不常见单词
查看>>
多线程,到底该设置多少个线程?
查看>>
第七节 存储过程 (含函数)
查看>>
gcc/g++等编译器 编译原理: 预处理,编译,汇编,链接各步骤详解
查看>>
vue常用指令
查看>>
JSP/SERVLET(2)——输出HTML模板
查看>>
BZOJ 1609 [Usaco2008 Feb]Eating Together麻烦的聚餐
查看>>
c#学习心得,慢慢添加,如果有错误希望大家留言,我刚开始学
查看>>
金蝶EAS常用表
查看>>