美文网首页
AFNetworking源码分析之图片缓存和下载

AFNetworking源码分析之图片缓存和下载

作者: hallfrita | 来源:发表于2018-10-17 15:13 被阅读0次

对于AFN中,还有一个比较优秀的设计,就是他对抽象类的一个使用。除了之前那些主要功能模块用到,他的一些附加类也使用到了。何为抽象类,就是一个主类下,有几个非主类,这些非主类就是抽象类。这些抽象类的主要作用是,使项目结构看起来更清晰,其实这些类的相关功能或属性其实完全可以在主类中做。

比如AFAutoPurgingImageCache里面有一个被缓存图片的抽象描述类AFCachedImage,这个类主要就是被缓存图片的一些属性,也就提供了一个属性的初始化方法

再比如AFImageDownloader里面的抽象类有AFImageDownloaderResponseHandler(处理图片下载完成之后的事件)、AFImageDownloaderMergedTask(封装了对同一个请求的处理)

下面我们就来分析一下,AFN中图片缓存和图片下载

图片缓存器

AFAutoPurgingImageCache的缓存是通过一个字典来缓存图片的(内存缓存)

@property (nonatomic, strong) NSMutableDictionary <NSString* , AFCachedImage*> *cachedImages;//存放图片

AFAutoPurgingImageCache的初始化方法

- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity {
    if (self = [super init]) {
        self.memoryCapacity = memoryCapacity;
        self.preferredMemoryUsageAfterPurge = preferredMemoryCapacity;
        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;
}

声明了一个串行队列。若没有特别设置,默认最大缓存100M,若收到内存警告,安全缓存是60M。

缓存器主要的操作就是对缓存字典的操作,对字典的操作必须保证线层安全,所以整个图片缓存缓存器的任务都是在同步串行队列中执行。保证线层安全的另外一种方式就是锁。

dispatch_barrier_async(self.synchronizationQueue, ^{
下面我们看看,这个类的核心代码

- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
    //添加缓存图片
    dispatch_barrier_async(self.synchronizationQueue, ^{
        AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];

        AFCachedImage *previousCachedImage = self.cachedImages[identifier];
        if (previousCachedImage != nil) {
            self.currentMemoryUsage -= previousCachedImage.totalBytes;
        }

        self.cachedImages[identifier] = cacheImage;
        self.currentMemoryUsage += cacheImage.totalBytes;
    });

    //100M currentMemoryUsage = 110M
    dispatch_barrier_async(self.synchronizationQueue, ^{
        if (self.currentMemoryUsage > self.memoryCapacity) {
            //bytesToPurge = 110-60 = 50
            UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge;
            NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues];
            NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate"
                                                                           ascending:YES];
            [sortedImages sortUsingDescriptors:@[sortDescriptor]];

            UInt64 bytesPurged = 0;

            for (AFCachedImage *cachedImage in sortedImages) {
                [self.cachedImages removeObjectForKey:cachedImage.identifier];
                //10M  20M  30M bytesPurged = 60M
                bytesPurged += cachedImage.totalBytes;
                if (bytesPurged >= bytesToPurge) {
                    break ;
                }
            }
            self.currentMemoryUsage -= bytesPurged;
        }
    });
}

这个方法做的事情大致就两个:把图片加入缓存字典中(注意字典中可能存在id的情况)、清缓存(当容量超过设置的最大容量)。

当容量超过设置的最大容量时
1.计算要清除的大小(也就是当前的大小-减去设置的安全大小(60M))
2.取出所有图片放到一个新数组
3.对这个新数组中的元素按最后访问时间进行升序
4.遍历数组,计算那些早期的图片大小,如果大小大于要清除的大小,就都清除,直到小于等于60M(也就是优先保留最新的图片缓存)

图片下载器

这个类提供了两种下载接口和一个取消任务的接口。初始化方法中,默认先进先出的下载顺序,最大下载任务4个。

初始化NSURLSessionConfiguration时用到了URLCache,URLCache的特点在于,提供了内存缓存和磁盘缓存,初始化中默认设置内存缓存20M,磁盘缓存150M。

+ (NSURLCache *)defaultURLCache {
    return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
                                         diskCapacity:150 * 1024 * 1024
                                             diskPath:@"com.alamofire.imagedownloader"];
}
- (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 {

上面的类就是该图片下载器核心的方法了。至于实现代码太多,就不copy了。为了线程安全,这里的任务处理放在了同步串行队列中执行。

主要做了下面几件事:
1、错误处理,下载请求有错误返回
2、判断是否有相同URL的请求,有的话将改任务的回调添加到对应任务的responseHandlers(保证多个相同任务,只做一次下载请求),然后返回
3、再判断缓存中是否有对应的图片,若缓存有取出图片直接返回
4、缓存中没有图片,就创建一个新任务(任务请求成功后,就利用AFAutoPurgingImageCache缓存该图片)
5、用新任务创建属于他的AFImageDownloaderMergedTask(合并任务)
6、判断当前使用的的请求数是否在最大限制下,若没有,开启当前任务,若超了,根据优先级策略添加数组中,等待下一次开启。

另一个方法就是取消任务的方法

- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt ;

取消任务也要在同步任务中执行。一个任务可能对应多个回调,放在同一个数组里。所以,要根据任务对应的凭证找到对饮的回调,然后取消这个任务。

附上一个用AFN下载图片的方法

- (void)downloadImage{
    AFImageDownloader *downloader = [[AFImageDownloader alloc] init];
    [downloader downloadImageForURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://img.haomeiwen.com/i810907/64ce170fba04d32a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"]] success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) {
        NSLog(@"图片下载成功");
    } failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) {
        
    }];
}

AFNetworkingReachability

使用demo

- (void)testReachability {
    AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager];
    [manager startMonitoring];
    [manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        switch (status) {
            case AFNetworkReachabilityStatusNotReachable:
                NSLog(@"网络状态不可用");
                break;
            case AFNetworkReachabilityStatusReachableViaWWAN:
                NSLog(@"wwan");
                break;
            case AFNetworkReachabilityStatusReachableViaWiFi:
                NSLog(@"wifi");
                      break;
            default:
                break;
        }
    }];
}

注意点:主机向外发送数据包,但不一定能连上服务器

相对于iOS提供的API,AFN提供了多种回调

相关文章

网友评论

      本文标题:AFNetworking源码分析之图片缓存和下载

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