美文网首页
AFNetworking源码学习之三-UIKit之UIImage

AFNetworking源码学习之三-UIKit之UIImage

作者: 会笑的Even | 来源:发表于2017-10-15 11:27 被阅读0次

iOS中给UIImageView设置网络图片时,通常用SDWebImage,但是AFNetworking也给了给UIImageView设置网络图片的方法,下面就具体看看其代码是怎么实现的.

实现主要有三个类实现的:AFAutoPurgingImageCache-内存缓存类,AFImageDownloader-图片下载类,UIImageView+AFNetworking - 设置网络图片UIImageView的分类.

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

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

- (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
{
    // 当url为空时
    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;

    // 从缓存中获取图片
    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;
    }
}

- (void)cancelImageDownloadTask {
    // 下载收据不为空时
    if (self.af_activeImageDownloadReceipt != nil) {
        [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt];
        [self clearActiveDownloadInformation];
     }
}

- (void)clearActiveDownloadInformation {
    self.af_activeImageDownloadReceipt = nil;
}

- (BOOL)isActiveTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest {
    return [self.af_activeImageDownloadReceipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString];
}

上面的代码的流程大致如下:
1.检查下的URL是否存在,不存,取消下载任务.
2.判断是否和当前的下载任务是否相同,如果相同直接返回.
3.检查缓存中是否有缓存,加载缓存.
4.如果没有缓存,就直接下载该图片.

从上面的代码可以看到UIImageView分类中添加了这个af_activeImageDownloadReceipt属性,不是分类中不能添加属性吗?确实分类中不能添加属性,因为在加载UIImageView类的时候,类的内存结构已经固定,但是可以通过runtime设置关联属性的方法,达到添加属性的功能,想MJRefresh中的mjfooter,mjheader都是通过关联属性实现的.

- (AFImageDownloadReceipt *)af_activeImageDownloadReceipt {
    return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, @selector(af_activeImageDownloadReceipt));
}

- (void)af_setActiveImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt {
    objc_setAssociatedObject(self, @selector(af_activeImageDownloadReceipt), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

这里用到了AFAutoPurgingImageCache实现内存缓存的功能,我们来看看其实现的细节:

- (instancetype)init {
    // 设置内存缓存为100MB,最优内存容量为60MB
    return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024];
}

- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity {
    if (self = [super init]) {
        self.memoryCapacity = memoryCapacity;
        self.preferredMemoryUsageAfterPurge = preferredMemoryCapacity;
        // 用NSMutableDictionary作为内存缓存
        self.cachedImages = [[NSMutableDictionary alloc] init];

        NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]];
        // 并发队列
        self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
        // 接受内存警告的通知
        [[NSNotificationCenter defaultCenter]
         addObserver:self
         selector:@selector(removeAllImages)
         name:UIApplicationDidReceiveMemoryWarningNotification
         object:nil];

    }
    return self;
}

我们可以看出这里是用NSMutableDictionary来作为内存存储图片,最大内存量为:100MB,最优内存量为:60MB,这里是最优内存存储量就是指,当内存量超过100MB,清理内存时,清理到60MB的内存量的水平.
内存缓存图片时,假如直接缓存图片的话,怎么标识图片的唯一性,如果内存使用量超过最大内存使用量时,怎么按照一定的淘汰规则(比如LRU)清理缓存等等,所以这里不能直接缓存图片,这里是缓存AFCachedImage对象.

@interface AFCachedImage : NSObject
// 图片
@property (nonatomic, strong) UIImage *image;
// 标识符
@property (nonatomic, strong) NSString *identifier;
// 大小
@property (nonatomic, assign) UInt64 totalBytes;
// 最后一次访问时间
@property (nonatomic, strong) NSDate *lastAccessDate;
// 当前内存缓存使用量
@property (nonatomic, assign) UInt64 currentMemoryUsage;

@end

@implementation AFCachedImage

-(instancetype)initWithImage:(UIImage *)image identifier:(NSString *)identifier {
    if (self = [self init]) {
        self.image = image;
        self.identifier = identifier;

        CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
      // 32位的颜色空间,4个字节组成
        CGFloat bytesPerPixel = 4.0;
        CGFloat bytesPerSize = imageSize.width * imageSize.height;
        // 计算图片的大小
        self.totalBytes = (UInt64)bytesPerPixel * (UInt64)bytesPerSize;
        // 最后一次访问时间
        self.lastAccessDate = [NSDate date];
    }
    return self;
}

上面初始化一些属性,比如lastAccessDate,totalBytes等.
回到之前具体获得缓存的图片的实现

- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier {
    return [self imageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]];
}

- (NSString *)imageCacheKeyFromURLRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)additionalIdentifier {
    NSString *key = request.URL.absoluteString;
    if (additionalIdentifier != nil) {
        key = [key stringByAppendingString:additionalIdentifier];
    }
    return key;
}

- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier {
    __block UIImage *image = nil;
    dispatch_sync(self.synchronizationQueue, ^{
        AFCachedImage *cachedImage = self.cachedImages[identifier];
        // 设置最后一次获取时间
        image = [cachedImage accessImage];
    });
    return image;
}

上面做的事就是根据key在self.cachedImages这个可变的字典中获取值,前面说了不是直接储存UIImage对象,是储存AFCachedImage对象,获取了AFCachedImage,要设置它最后一次访问时间.

再看下载图片的代码:

 (NSURLCache *)defaultURLCache {
    // 设置内存缓存为20MB,磁盘缓存为150MB
    return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
                                         diskCapacity:150 * 1024 * 1024
                                             diskPath:@"com.alamofire.imagedownloader"];
}

+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration {
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

    
    // 设置cookis为yes
    configuration.HTTPShouldSetCookies = YES;
    // 设置https认证为No
    configuration.HTTPShouldUsePipelining = NO;
    // 设置缓存策略
    configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
    // 设置允许蜂窝访问
    configuration.allowsCellularAccess = YES;
    // 设置超时时间为60s
    configuration.timeoutIntervalForRequest = 60.0;
    // 设置网络缓存
    configuration.URLCache = [AFImageDownloader defaultURLCache];

    return configuration;
}

- (instancetype)init {
    // 初始化默认session配置
    NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
    // 初始一个sessionManager
    AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:defaultConfiguration];
    // 设置响应序列化方式为图片响应序列化
    sessionManager.responseSerializer = [AFImageResponseSerializer serializer];
    // 设置下载的优先级为先进先出,最大下载数量为4个,内存缓存为AFAutoPurgingImageCache
    return [self initWithSessionManager:sessionManager
                 downloadPrioritization:AFImageDownloadPrioritizationFIFO
                 maximumActiveDownloads:4
                             imageCache:[[AFAutoPurgingImageCache alloc] init]];
}

- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager
                downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization
                maximumActiveDownloads:(NSInteger)maximumActiveDownloads
                            imageCache:(id <AFImageRequestCache>)imageCache {
    if (self = [super init]) {
        self.sessionManager = sessionManager;

        self.downloadPrioritizaton = downloadPrioritization;
        self.maximumActiveDownloads = maximumActiveDownloads;
        self.imageCache = imageCache;
        // 入队合并任务
        self.queuedMergedTasks = [[NSMutableArray alloc] init];
        // 合并任务
        self.mergedTasks = [[NSMutableDictionary alloc] init];
        // 活跃请求数量
        self.activeRequestCount = 0;

        NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]];
        // 串行队列
        self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);

        name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]];
        // 并发响应队列
        self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);
    }

    return self;
}

+ (instancetype)defaultInstance {
    // 设置AFImageDownloader单例
    static AFImageDownloader *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request
                                                  withReceiptID:(nonnull NSUUID *)receiptID
                                                        success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse  * _Nullable response, UIImage *responseObject))success
                                                        failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure {
    __block NSURLSessionDataTask *task = nil;
    dispatch_sync(self.synchronizationQueue, ^{
        NSString *URLIdentifier = request.URL.absoluteString;
        // 如果URL为空直接返回
        if (URLIdentifier == nil) {
            if (failure) {
                NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
                dispatch_async(dispatch_get_main_queue(), ^{
                    failure(request, nil, error);
                });
            }
            return;
        }

        // 如果当前下载任务,已经下载过
        AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier];
        // 把failure和successblock生成一个handler加入到当前已存在的任务的responseHandlers数组中
        if (existingMergedTask != nil) {
            AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure];
            [existingMergedTask addResponseHandler:handler];
            task = existingMergedTask.task;
            return;
        }

        // 如果内存缓存有,就从内存缓存加载图片
        switch (request.cachePolicy) {
            // 如果缓存策略为NSURLRequestUseProtocolCachePolicy,NSURLRequestReturnCacheDataElseLoad,NSURLRequestReturnCacheDataDontLoad三种的一种,即从缓存中加载图片
            case NSURLRequestUseProtocolCachePolicy:
            case NSURLRequestReturnCacheDataElseLoad:
            case NSURLRequestReturnCacheDataDontLoad: {
                UIImage *cachedImage = [self.imageCache imageforRequest:request withAdditionalIdentifier:nil];
                // 如果缓存图片存在
                if (cachedImage != nil) {
                    if (success) {
                        dispatch_async(dispatch_get_main_queue(), ^{
                            success(request, nil, cachedImage);
                        });
                    }
                    return;
                }
                break;
            }
            default:
                break;
        }

        // 创建下载请求
        NSUUID *mergedTaskIdentifier = [NSUUID UUID];
        NSURLSessionDataTask *createdTask;
        __weak __typeof__(self) weakSelf = self;

        createdTask = [self.sessionManager
                       dataTaskWithRequest:request
                       completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                           dispatch_async(self.responseQueue, ^{
                               __strong __typeof__(weakSelf) strongSelf = weakSelf;
                               AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier];
                               // mergedTask的UUID和当前UUID相同
                               if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) {
                                   // 移除task
                                   mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier];
                                   if (error) {
                                       // 回调改task1的所有handler
                                       for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
                                           if (handler.failureBlock) {
                                               dispatch_async(dispatch_get_main_queue(), ^{
                                                   handler.failureBlock(request, (NSHTTPURLResponse*)response, error);
                                               });
                                           }
                                       }
                                   } else {
                                       // 把当前图片添加到缓存图片中
                                       [strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil];
                                       // 回调所有handler的successBlock
                                       for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) {
                                           if (handler.successBlock) {
                                               dispatch_async(dispatch_get_main_queue(), ^{
                                                   handler.successBlock(request, (NSHTTPURLResponse*)response, responseObject);
                                               });
                                           }
                                       }
                                       
                                   }
                               }
                               // 减少正在活跃的任务数量
                               [strongSelf safelyDecrementActiveTaskCount];
                               // 开启下一个任务
                               [strongSelf safelyStartNextTaskIfNecessary];
                           });
                       }];

        // 生成一个handler
        AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID
                                                                                                   success:success
                                                                                                   failure:failure];
        // 生成一个mergedTask
        AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc]
                                                   initWithURLIdentifier:URLIdentifier
                                                   identifier:mergedTaskIdentifier
                                                   task:createdTask];
        [mergedTask addResponseHandler:handler];
        self.mergedTasks[URLIdentifier] = mergedTask;

        // 5) Either start the request or enqueue it depending on the current active request count
        // 如果当前任务数量小于最大任务数量
        if ([self isActiveRequestCountBelowMaximumLimit]) {
            [self startMergedTask:mergedTask];
        } else {
            // 否则把当前任务根据下载优先级加入到下载队列中
            [self enqueueMergedTask:mergedTask];
        }

        task = mergedTask.task;
    });
    if (task) {
        return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task];
    } else {
        return nil;
    }
}

可以看出这里设置了URL级别的NSURLCache缓存,内存缓存为20MB,磁盘缓存为150MB,这里的内存缓存,和前面的内存缓存互不干扰,但是这里用了NSURLCache缓存,该缓存只对POS请求有效,请他请求方式就无能为力了.
创建的下载任务也不是马上就开始下载的,这里设置了最大下载数量为4个,如果当前下载数量小于4个就开始下载,否则根据下载的优先级,这里给出了两种优先级:

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

如果下载优先级为AFImageDownloadPrioritizationFIFO,将任务放在队列最后,如果优先级为AFImageDownloadPrioritizationLIFO,将任务放在第一个.

相关文章

网友评论

      本文标题:AFNetworking源码学习之三-UIKit之UIImage

      本文链接:https://www.haomeiwen.com/subject/qlkwyxtx.html