• AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking


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

    前言

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

    UIButton+AFNetworking

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

    其中核心方法为:

    示例代码:

    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

  • 相关阅读:
    算法(Algorithms)第4版 练习 1.3.4
    算法(Algorithms)第4版 练习 1.3.5
    算法(Algorithms)第4版 练习 1.3.6
    微服务迁移步骤建议
    xxxx interview问题解读与总结
    乐观锁与悲观锁——解决并发问题
    编码解码--三种常见字符编码简介:ASCII、Unicode和UTF-8
    编码解码--url编码解码
    分页缓存预加载算法
    Could not initialize class sun.awt.X11FontManager 异常问题
  • 原文地址:https://www.cnblogs.com/machao/p/5821462.html
Copyright © 2020-2023  润新知