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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM