对于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提供了多种回调
网友评论