AFNetWorking 下載圖片過程閱讀筆記

###AFNetWorking ****下載圖片過程

[self.imageView setImageWithURL:[NSURL URLWithString:item.picture] placeholderImage:[UIImage imageNamed:@"PTV_Normal_Default_Icon"]];

比如上面的執行過程:

會調用如下:

- (void)setImageWithURL:(NSURL *)url
       placeholderImage:(UIImage *)placeholderImage
{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];

    [self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}

生成 request,并設置http 請求頭 accept 字段值為 image/*

我們都知道,http header 消息通常被分為4個部分:general header, request header, response header, entity header。但是這種分法就理解而言,感覺界限不太明確。根據維基百科對http header內容的組織形式,大體分為Request和Response兩部分。

<strong>Accept 字段 表示:指定客戶端能夠接收的內容類型 </strong>
http 頭中還有一個比較常用的字段 Cache-Control,用來指定緩存策略。
關于 http 頭的,詳見 httpHead詳解 w3c官網Header Field Definitions

扯遠了,不過看一遍還是有幫助的。

下載過程如下:

- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
              placeholderImage:(UIImage *)placeholderImage
                       success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
                       failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure
{

    if ([urlRequest URL] == nil) {
        [self cancelImageDownloadTask];
        self.image = placeholderImage;
        return;
    }

    if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){
        return;
    }

    [self cancelImageDownloadTask];

    AFImageDownloader *downloader = [[self class] sharedImageDownloader];
    id <AFImageRequestCache> imageCache = downloader.imageCache;

    //Use the image from the image cache if it exists
    UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
    if (cachedImage) {
        if (success) {
            success(urlRequest, nil, cachedImage);
        } else {
            self.image = cachedImage;
        }
        [self clearActiveDownloadInformation];
    } else {
        if (placeholderImage) {
            self.image = placeholderImage;
        }

        __weak __typeof(self)weakSelf = self;
        NSUUID *downloadID = [NSUUID UUID];
        AFImageDownloadReceipt *receipt;
        receipt = [downloader
                   downloadImageForURLRequest:urlRequest
                   withReceiptID:downloadID
                   success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) {
                       __strong __typeof(weakSelf)strongSelf = weakSelf;
                       if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
                           if (success) {
                               success(request, response, responseObject);
                           } else if(responseObject) {
                               strongSelf.image = responseObject;
                           }
                           [strongSelf clearActiveDownloadInformation];
                       }

                   }
                   failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
                       __strong __typeof(weakSelf)strongSelf = weakSelf;
                        if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) {
                            if (failure) {
                                failure(request, response, error);
                            }
                            [strongSelf clearActiveDownloadInformation];
                        }
                   }];

        self.af_activeImageDownloadReceipt = receipt;
    }
}

###****接下來分析一下上面那個長函數

AFNetworking 的所有圖片下載都是通過同一個downloader 實例完成的,這里用的是默認的。

AFImageDownloader *downloader = [[self class] sharedImageDownloader];

+ (AFImageDownloader *)sharedImageDownloader {
    return objc_getAssociatedObject(self, @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance];
}

+ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader {
    objc_setAssociatedObject(self, @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

這里通過關聯和 class 綁定的。這里的 [AFImageDownloader defaultInstance] 就有一些默認的配置。比如 defaultURLCache等等。

但是下載過程還是通過AF的組件 AFHTTPSessionManager 來完成的。

- (instancetype)init {
    NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
    AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:defaultConfiguration];
    sessionManager.responseSerializer = [AFImageResponseSerializer serializer];

    return [self initWithSessionManager:sessionManager
                 downloadPrioritization:AFImageDownloadPrioritizationFIFO
                 maximumActiveDownloads:4
                             imageCache:[[AFAutoPurgingImageCache alloc] init]];
}

最重要的,這里解析數據的指定成了圖片解析。

sessionManager.responseSerializer = [AFImageResponseSerializer serializer];

AFImageResponseSerializer 提供了圖片解碼的過程。

AFHTTPSessionManager 優秀的設計方案

@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;

這里請求和響應的property 都是遵守某個協議的,方便對不同類型的請求做不同的處理,比如:xml json, image. <strong>這在我們平常的類中可以參考</strong>

獲取到了下載實例,接下來嘗試從緩存中獲取圖片

UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil];
    if (cachedImage) {
        if (success) {
            success(urlRequest, nil, cachedImage);
        } else {
            self.image = cachedImage;
        }
        
        //代表下載完成。
        [self clearActiveDownloadInformation];

如果沒有緩存,就通過AFImageDownloader 下載數據,因為解析出來的數據已經處理過了,所以直接賦值image 就可以了!

###****緩存篇
這里緩存默認使用的是 NSCache..

+ (NSURLCache *)defaultURLCache {
    // It's been discovered that a crash will occur on certain versions
    // of iOS if you customize the cache.
    //
    // More info can be found here: https://devforums.apple.com/message/1102182#1102182
    //
    // When iOS 7 support is dropped, this should be modified to use
    // NSProcessInfo methods instead.
    if ([[[UIDevice currentDevice] systemVersion] compare:@"8.2" options:NSNumericSearch] == NSOrderedAscending) {
        return [NSURLCache sharedURLCache];
    }
    return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
                                         diskCapacity:150 * 1024 * 1024
                                             diskPath:@"com.alamofire.imagedownloader"];
}

我在本地項目試了一下如下測試代碼:

NSLog(@"????????  %lu %lu",(unsigned long)[NSURLCache sharedURLCache].memoryCapacity, (unsigned long)[NSURLCache sharedURLCache].diskCapacity);

輸出如下:

2017-09-06 17:30:32.928311+0800 PandaTV-HD[1173:464474] ????????  512000 10000000

也就是說,其實默認就已經設置好了512kb的內存緩存空間,以及10MB的磁盤緩存空間。可能你的代碼中并沒有寫任何與NSURLCache有關的東西,但其實它已經默默的開始幫你進行緩存了。

關于 NSCache 詳見兩篇比較好的博客 DIY圖片緩存庫 NSUrlCache詳解

###****下載隊列篇
下載隊列創建如下:

        self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);

創建的是一個串行隊列。

配合

typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) {
    AFImageDownloadPrioritizationFIFO,
    AFImageDownloadPrioritizationLIFO
};

可以實現,下載隊列的順序,比如:

- (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask {
    switch (self.downloadPrioritizaton) {
        case AFImageDownloadPrioritizationFIFO:
            [self.queuedMergedTasks addObject:mergedTask];
            break;
        case AFImageDownloadPrioritizationLIFO:
            [self.queuedMergedTasks insertObject:mergedTask atIndex:0];
            break;
    }
}

self.queuedMergedTasks 是一個數組。

在上個一請求完成,會嘗試開啟下一個請求。

- (void)safelyStartNextTaskIfNecessary {
    dispatch_sync(self.synchronizationQueue, ^{
        if ([self isActiveRequestCountBelowMaximumLimit]) {
            while (self.queuedMergedTasks.count > 0) {
                AFImageDownloaderMergedTask *mergedTask = [self dequeueMergedTask];
                if (mergedTask.task.state == NSURLSessionTaskStateSuspended) {
                    [self startMergedTask:mergedTask];
                    break;
                }
            }
        }
    });
}

請求都是在同一個線程里 同步進行的,所以不用使用NSLock 這樣子的東東。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容