###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 這樣子的東東。