网络编程

当前位置:永利402游戏网站-永利402com官方网站 > 网络编程 > 老驾车员出品———疯狂造轮子之图片异步下载

老驾车员出品———疯狂造轮子之图片异步下载

来源:http://www.xtcsyb.com 作者:永利402游戏网站-永利402com官方网站 时间:2019-09-11 14:45

图片 1图形异步下载类

1. 入口setImageWithUrl:placeHolderImage:options:会把placeHolderImage显示,然后SDWebImageManager根据URL开始处理图片.2. 进入SDWebImageManager-downloadWithURL:delegate:options:userInfo:交给SDImageCache从缓存查找图片是否已经下载queryDiskCacheForKey:delegate:userInfo:3. 先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate回调imageCache:didFineImage:forKey:userInfo:到SDWebImageManager.4. SDWebImageManagerDelegate回调webImageManager:didFinishWithImage:到UIImageView + WebCache等前端展示图片.5. 如果内存缓存中没有,生成NSInvocationOperation添加到队列开始从硬盘查找图片是否已经缓存6. 根据URLKey在硬盘缓存目录下尝试读取图片文件.这一步是在NSOperation进行的操作,所以回主线程进行结果回调notifyDelegate.7. 如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小 会先清空内存缓存).SDImageCacheDelegate 回调imageCache:didFinishImage:forKey:userInfo:进而回调展示图片.8. 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调imageCache:didNotFindImageForKey:userInfo.9. 共享或重新生成一个下载器SDWebImageDownLoader开始下载图片10. 图片下载由NSURLConnection来做,实现相关delegate来判断图片下载中,下载完成和下载失败11. connection:didReceiveData:中利用ImageIO做了按图片下载进度加载效果12. connectionDidFinishLoading:数据下载完成后交给SDWebImageDecoder做图片解码处理13. 图片解码处理在一个NSOperationQueue完成,不会拖慢主线程UI.如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多.14. 在主线程notifyDelegateOnMainThreadWithInfo:宣告解码完成imageDecoder:didFinishDecodingImage:userInfo:回调给SDWebImageDownloader15. imageDownLoader:didFinishWithImage:回调给SDWebImageManager告知图片下载完成16. 通知所有的downloadDelegates下载完成,回调给需要的地方展示图片17. 将图片保存到SDImageCache中内存缓存和硬盘缓存同时保存,写文件到硬盘也在以单独NSInvocationOperation完成,避免拖慢主线程18. SDImageCache在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片19. SDWI也提供UIButton + WebCache和MKAnnptation + WebCache方便使用20. SDWebImagePrefetcher 可以预先下载图片,方便后续使用

SDWebImage,作者估摸未有二个做iOS的不精通那一个三方库吧,他为我们提供了凝练的图纸异步下载情势。在她为本人一句api带来这么大方便的还要,你有未有想过他是怎么落到实处的吧?让大家先来看看他为大家做了怎样?

再用一张图表明:

  • 图表异步加载
  • 图形缓存
  • 图表编解码
  • 图形渐进式下载
  • 下载职责管理

图片 2

So,你认为作者要给您讲讲SDWebImage实现原理?NONONO!SD这么三个成熟的框架已经有为数相当多人对其架构实行过彻底的剖析,老车手说了也是一模一样的,但作为程序猿最快的成年人就是连连地重造轮子。当然你造轮子不自然是要取代原先的,只是扩张一种思路。

1.2.1 SDWebImageOptions:图片下载计策

例如,SD为UIImageView提供的UIImageView+WebCache.m分类,有这些API:

- sd_setImageWithURL:url { [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];}- sd_setImageWithURL:url placeholderImage:(UIImage *)placeholder { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];}- sd_setImageWithURL:url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];}- sd_setImageWithURL:url completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];}- sd_setImageWithURL:url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];}- sd_setImageWithURL:url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock { [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];}

事实上,以上那么些API都一向或直接利用到了SDWebImageOptions这几个参数,那么你记念那些参数有怎么样项目?各有怎样效益?

通过查阅SDWebImageManager.h源代码,可见如下:

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { // 默认情况下,当URL下载失败时,URL会被列入黑名单,导致库不会再去重试,该标记用于禁用黑名单 SDWebImageRetryFailed = 1 << 0, // 默认情况下,图片下载开始于UI交互,该标记禁用这一特性,这样下载延迟到UIScrollView减速时 SDWebImageLowPriority = 1 << 1, // 该标记禁用磁盘缓存 SDWebImageCacheMemoryOnly = 1 << 2, // 该标记启用渐进式下载,图片在下载过程中是渐渐显示的,如同浏览器一下。 // 默认情况下,图像在下载完成后一次性显示 SDWebImageProgressiveDownload = 1 << 3, // 即使图片缓存了,也期望HTTP响应cache control,并在需要的情况下从远程刷新图片。 // 磁盘缓存将被NSURLCache处理而不是SDWebImage,因为SDWebImage会导致轻微的性能下载。 // 该标记帮助处理在相同请求URL后面改变的图片。如果缓存图片被刷新,则完成block会使用缓存图片调用一次 // 然后再用最终图片调用一次 SDWebImageRefreshCached = 1 << 4, // 在iOS 4+系统中,当程序进入后台后继续下载图片。这将要求系统给予额外的时间让请求完成 // 如果后台任务超时,则操作被取消 SDWebImageContinueInBackground = 1 << 5, // 通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES;来处理存储在NSHTTPCookieStore中的cookie SDWebImageHandleCookies = 1 << 6, // 允许不受信任的SSL认证 SDWebImageAllowInvalidSSLCertificates = 1 << 7, // 默认情况下,图片下载按入队的顺序来执行。该标记将其移到队列的前面, // 以便图片能立即下载而不是等到当前队列被加载 SDWebImageHighPriority = 1 << 8, // 默认情况下,占位图片在加载图片的同时被加载。该标记延迟占位图片的加载直到图片已以被加载完成 SDWebImageDelayPlaceholder = 1 << 9, // 通常我们不调用动画图片的transformDownloadedImage代理方法,因为大多数转换代码可以管理它。 // 使用这个票房则不任何情况下都进行转换。 SDWebImageTransformAnimatedImage = 1 << 10,};

据此,今日老手就带着您来实现一个简单的图片下载类

1.2.2 SDImageCacheType:图片缓存战略

诸如,设置图片的七个例证

[self.image2 sd_setImageWithURL:imagePath2 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { NSLog(@"这里可以在图片加载完成之后做些事情");}];

//使用默认图片,而且用block 在完成后做一些事情[self.image1 sd_setImageWithURL:imagePath1 placeholderImage:[UIImage imageNamed:@"default"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { NSLog(@"图片加载完成后做的事情");}];

再有得到下载进程的事例

//使用默认图片,而且用block 在完成后做一些事情[self.image1 sd_setImageWithURL:imagePath1 placeholderImage:[UIImage imageNamed:@"default"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { NSLog(@"图片加载完成后做的事情"); }];

上边的例证,都有个SDImageCacheTyp的参数,你记得这几个参数有怎么样?

  • SDImageCacheType
//定义Cache类型typedef NS_ENUM(NSInteger, SDImageCacheType) {//不使用cache获得图片,依然会从web下载图片 SDImageCacheTypeNone,//图片从disk获得 SDImageCacheTypeDisk,//图片从Memory中获得 SDImageCacheTypeMemory};
  • SDImageCache类的源码
//这个变量默认值为YES,显示比较高质量的图片,但是会浪费比较多的内存,可以通过设置NO来缓解内存@property (assign, nonatomic) BOOL shouldDecompressImages;//总共的内存允许图片的消耗值@property (assign, nonatomic) NSUInteger maxMemoryCost;//图片存活于内存的时间初始化的时候默认为一周@property (assign, nonatomic) NSInteger maxCacheAge;//每次存储图片大小的限制@property (assign, nonatomic) NSUInteger maxCacheSize;
  • 设置maxCacheSize的例子
SDWebImageManager *manager = [SDWebImageManager sharedManager];[manager.imageCache setMaxMemoryCost:1000000];//设置总缓存大小,默认为0没有限制[manager.imageCache setMaxCacheSize:640000];//设置单个图片限制大小[manager.imageDownloader setMaxConcurrentDownloads:1];//设置同时下载线程数,这是下载器的内容,下面将会介绍[manager downloadImageWithURL:[NSURL URLWithString:@"http://p9.qhimg.com/t01eb74a44c2eb43193.jpg"] options:SDWebImageProgressiveDownload progress:^(NSInteger receivedSize, NSInteger expectedSize) { NSLog(@"%lu", receivedSize); } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { self.imageView1.image = image; }];[manager downloadImageWithURL:[NSURL URLWithString:@"http://img.article.pchome.net/00/28/33/87/pic_lib/wm/kuanpin12.jpg"] options:SDWebImageProgressiveDownload progress:^(NSInteger receivedSize, NSInteger expectedSize) { NSLog(@"%lu", receivedSize); } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { self.imageView2.image = image; }];NSUInteger size = [manager.imageCache getSize];NSUInteger count = [manager.imageCache getDiskCount];NSLog(@"size = %lu", size); // 644621NSLog(@"count = %lu", count); // 2[manager.imageCache clearDisk];size = [manager.imageCache getSize];count = [manager.imageCache getDiskCount];NSLog(@"sizeClean = %lu", size); // 0NSLog(@"countClean = %lu", count); // 0 这里使用的是clear
  • 先查看内部存储器图片缓存,内部存款和储蓄器图片缓存未有,后变化操作,查看磁盘图片缓存
  • 磁盘图片缓存有,就加载到内部存款和储蓄器缓存,未有就下载图片
  • 在成立下载操作在此以前,判别下载操作是不是留存
  • 暗许情形下,下载的图形数据会同不时候缓存到内部存款和储蓄器和磁盘中

图片 3

有关缓存地点

  • 内部存款和储蓄器缓存是经过 NSCache的子类AutoPurgeCache来完毕的;
  • 磁盘缓存是通过 NSFileManager 来落实文件的贮存(暗中同意路线为/Library/Caches/default/com.hackemist.SDWebImageCache.default),是异步落成的。

至于图片下载操作

SDWebImage的比比较多工作是由缓存对象SDImageCache和异步下载器管理对象SDWebImageManager来完结的。

SDWebImage的图片下载是由SDWebImageDownloader这些类来贯彻的,它是一个异步下载管理器,下载进度中追加了对图片加载做了优化的拍卖。而真的兑现图片下载的是自定义的一个Operation操作,将该操作加入到下载管理器的操作队列downloadQueue中,Operation操作依赖系统提供的NSU奥迪Q5LConnection类完毕图片的下载。

  • 网络判别的主题材料--利用AFNetworking的API首先,启用监察和控制
// AppDelegate.m 文件中- application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 监控网络状态 [[AFNetworkReachabilityManager sharedManager] startMonitoring];}

下一场,在必要的地点获得监察和控制管理

 // 以下代码在需要监听网络状态的方法中使用 AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager]; if (mgr.isReachableViaWiFi) { // 在使用Wifi, 下载原图 } else { // 其他,下载小图 }
  • 并无法轻松的这么:WIFI就下载高清图,蜂窝网络就下载缩略图。要怀念和选择缓存的因素。

  • 三个特出例证

- setItem:(CustomItem *)item{ _item = item; // 占位图片 UIImage *placeholder = [UIImage imageNamed:@"placeholderImage"]; // 从内存沙盒缓存中获得原图, UIImage *originalImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.originalImage]; if (originalImage) { // 如果内存沙盒缓存有原图,那么就直接显示原图(不管现在是什么网络状态) self.imageView.image = originalImage; } else { // 内存沙盒缓存没有原图 AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager]; if (mgr.isReachableViaWiFi) { // 在使用Wifi, 下载原图 [self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder]; } else if (mgr.isReachableViaWWAN) { // 在使用手机自带网络 // 用户的配置项假设利用NSUserDefaults存储到了沙盒中 // [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"alwaysDownloadOriginalImage"]; // [[NSUserDefaults standardUserDefaults] synchronize];#warning 从沙盒中读取用户的配置项:在3G4G环境是否仍然下载原图 BOOL alwaysDownloadOriginalImage = [[NSUserDefaults standardUserDefaults] boolForKey:@"alwaysDownloadOriginalImage"]; if (alwaysDownloadOriginalImage) { // 下载原图 [self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder]; } else { // 下载小图 [self.imageView sd_setImageWithURL:[NSURL URLWithString:item.thumbnailImage] placeholderImage:placeholder]; } } else { // 没有网络 UIImage *thumbnailImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.thumbnailImage]; if (thumbnailImage) { // 内存沙盒缓存中有小图 self.imageView.image = thumbnailImage; } else { // 处理离线状态,而且有没有缓存时的情况 self.imageView.image = placeholder; } } }}
  • 思路,改写sd_imageWithData方法的源代码,可参照
  • SD为设置UIImageView提供的API,百川归海调用的是上边API:
- sd_setImageWithURL:url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
  • 它的措施完结体中,第一句话便是[self sd_cancelCurrentImageLoad];
  • 以此极度关键,当在TableView的cell包涵了的UIImageView被选定期,首先调用这一行代码,保证这几个ImageView的下载和缓存组合操作都被打消。假若:①上次赋值的图片正在下载,则下载不再进行;②下载完毕了,但还一直不推行到调用回调(回调包罗wself.image

    image),由于操作被吊销,因此不会议及展览示和重用的cell同样的图形;③上述两种状态独有在网速非常的慢和手提式有线电话机管理速度非常的慢的动静下才会时有发生,实际上发生的概率比很小,大多数是这种景观:操作已经实行到下载完毕了,此次使用的cell是一个援引的cell,而且保存着imageView的image,对于这种情况SD会在该兑现形式里面接着设置占位图的言辞,将image临时设置为占位图,假如占位图为空,就表示先一时清空image。

图片 4

  • SD内部移除绑定的操作的调用栈为:
- sd_cancelCurrentImageLoad { [self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"];}

- sd_cancelImageLoadOperationWithKey:(NSString *)key { // Cancel in progress downloader from queue NSMutableDictionary *operationDictionary = [self operationDictionary]; id operations = [operationDictionary objectForKey:key]; if (operations) { if ([operations isKindOfClass:[NSArray class]]) { for (id <SDWebImageOperation> operation in operations) { if (operation) { [operation cancel]; } } } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){ [(id<SDWebImageOperation>) operations cancel]; } [operationDictionary removeObjectForKey:key]; }}

(至少二零一六年12月的版本)老版本的基于 NSURLConnectionSDWebImage 是通过如此的编写制定:NSU中华VLConnection专门的职业在主线程,即便NSUExigeLConnection专门的学问在子线程,但因为UI相关的操作和回调中的setImage都在同多少个主线程,滑动荧屏会产生主线程的runloop切换mode为UITrackingRunLoopMode,此时原来kCFRunLoopDefaultMode上的source (这里是NSURLConnectionsetImage) 都被暂停,runloop实践不到回调。

它的本意是不让网络相关的操作阻塞到主线程,考订:互联网有关的操作在子线程,主线程runloop的mode切换并不会影响子线程,可是它那样设计真正有那般的功能:显示器滑动时,暂停数据下载的任务,改进:滑动显示屏并不会半上落下数据下载,暂停的是同八个主线程的setImage

在老版本中,在SDWebImageDownloaderOperation.m文件中有如此一段话:

图片 5SDWebImageDownloaderOperation.m

而是,新本子的 SDWebImage 是基于 NSURLSession 的,这么些NSU锐界LSession分歧于NSU汉兰达LConnection的最大不一致是或不是基于主线程 子线程 的runloop调控的,而是经过NSOperation新开子线程,所以同意主线程的runloop切换mode并不会影响子线程的操作。所以,新版本的SDWebImage是没有这个“滑动即暂停”的效果的。考订:同样,滑动显示屏并不会搁浅数据下载,暂停的是同贰个主线程的setImage

假诺,实在有亟待,有二种格局,可以自己改写setImage的方法,在里面设置工作的mode,同老版的SDWebImage一样考订:一种是退换setImage的线程恐怕mode。还会有一种艺术,能够监听ScrollView的拉拽状态,当ScrollView的代理方法监听到被拉拽,就suspend操作。

让我们先深入分析以下大家到底需求些什么?

图片 6下载思路

这是一个整机的图片下载思路,编解码等图片管理的老驾车员并未有纳在其间,因为供给详细的图片编解码知识技术读懂代码,何况本期教程也至关心敬爱要整理下载思路。

骨子里有了地点的剖判大家必要做的就很明朗了。

  • 率先大家须要二个图片下载类,为大家进行图片下载职务,并在做到时实行有关回调。
  • 说不上我们须求贰个图片缓存类,图片下载完成时将图片举办缓存。
  • 末尾大家需求贰个下载任务管理类,支持大家管理当前下载职分,幸免双重下载。

那大家接下去一一深入分析相关须要。

图形下载类

事实上要写贰个下载类,大家的思路应当很显明。既然是多少央求,大家本来应该及时想到NSURLSession做下载。

NSU本田UR-VLSession是iOS7生产的与NSU奥迪Q5LConnection并列的互联网央浼库,並且在iOS9中苹果发表取消NSU奇骏LConnection,NSU奇骏LSession从此正式走入历史舞台,大许多还在保卫安全的互联网有关的三方库都追随苹果的脚步将底层Api替换为NSULANDLSession相关。————引自《老鸟瞎逼逼》第一卷第一章第一篇第一行第一句

那么大家来使用NSU揽胜极光LSession写二个下载类。

NSU索罗德LSession其实是一个对话,管理着发生在其上述的具有数据沟通职责。一个会话可以同时管理多个数据请求。何况NSUPRADOLSession还向咱们提供了指定任务回调的队列的Api,让大家有利的抉择在主线程或子线程中回调。

一般来说,未有新鲜须要,大家相应尽量复用我们的对话,毕竟频仍的创导与自由对象都以系统资源上的浪费。

NSUOdysseyLSession为我们提供了二种最初化情势

+sessionWithConfiguration:+sessionWithConfiguration:delegate:delegateQueue:

这里能够依据不一样的须求选取对应粒度的Api举办伊始化。

内部Configuration那几个参数我们可以传进去一个安顿对象,来定制大家session会话的两样参数。这里系统为大家预置了3中配置

defaultSessionConfiguration

暗许配置利用的是长久化的硬盘缓存,存款和储蓄证书到顾客钥匙链。存储cookie到shareCookie。

申明:假诺想要移植原本基于NSU本田UR-VLConnect的代码到NSU汉兰达LSession,可应用该私下认可配置,然后再依据须求定制该暗中同意配置。

ephemeralSessionConfiguration

重临一个不适用恒久持存cookie、证书、缓存的安插,最好优化数据传输。

注解:当程序作废session时,全数的ephemeral session 数据会马上排除。其余,假设你的程序处于停顿状态,内存数据也许不会即时解除,不过会在程序终止或许接到内部存款和储蓄器警告大概内部存款和储蓄器压力时即刻排除。

backgroundSessionConfigurationWithIdentifier

转移贰个方可上传下载HTTP和HTTPS的后台义务。在后台时,将网络传输交给系统的单独的叁个进度。

要害:identifier 是configuration的独一标示,不能够为空或nil

摘自 NSURLSessionConfiguration API详解

此地大家接纳暗中认可配置单独设置一下央求超时时间长度就能够。

图片 7NSURLSession

有了session对象,大家即能够request初步化NSU纳瓦拉LSessionTask对象来做数据交流。

NSUEnclaveLSessionUploadTask:上传用的Task,传完之后不会再下载再次回到结果;

NSUSportageLSessionDownloadTask:下载用的Task;

NSUKoleosLSessionDataTask:可以上传内容,上传实现后再开展下载。

引自NSU瑞虎LSession使用验证及后台专门的学问流程深入分析

有了地点四个参照他事他说加以考察资料,这里笔者借使你早就能使用NSUEvoqueLSession了(毕竟那不是自身明天的大旨),鉴于作者不爱护下载进程,只关怀下载结果,所以本身采纳了最简便直接的Api。

图片 8Task

可以看出,老车手在现今成功的回调中一同做了以下几件事:

  • 稽查是或不是下载失利,若战败,抛出错误新闻
  • 若成功取到UIImage对象,使用缓存类举办多少缓存
  • 遍历回调数组进行回调

代码都很轻巧,也不用多做表明,这样大家的下载类就成功了。

放一下下载类的凡事代码

#pragma mark --- 图片下载类 ---@interface DWWebImageDownloader : NSObject///回调数组@property (nonatomic ,strong) NSMutableArray <DWWebImageCallBack>* callBacks;///下载任务@property (nonatomic ,strong) NSURLSessionDataTask * task;///下载图像实例/** 任务完成前为nil */@property (nonatomic ,strong) UIImage * image;///现在完成标志@property (nonatomic ,assign) BOOL downloadFinish;///初始化方法-(instancetype)initWithSession:(NSURLSession *)session;///以url下载图片-downloadImageWithUrlString:(NSString *)url;///开启下载-resume;///取消下载-cancel;@end

#pragma mark --- DWWebImageDownloader ---@interface DWWebImageDownloader ()@property (nonatomic ,copy) NSString * url;@property (nonatomic ,strong) NSURLSession * session;@end@implementation DWWebImageDownloader#pragma mark --- 接口方法 ----(instancetype)initWithSession:(NSURLSession *)session { self = [super init]; if  { _session = session; _downloadFinish = NO; } return self;}-downloadImageWithUrlString:(NSString *)url{ if (!url.length) { dispatch_async_main_safe{ [[NSNotificationCenter defaultCenter] postNotificationName:DWWebImageDownloadFinishNotification object:nil userInfo:@{@"error":DWErrorWithDescription(10001,@"url为空"),@"url":self.url}]; })); return; } [self downloadImageWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];}-resume { [self.task resume];}-cancel { [self.task cancel];}#pragma mark --- Tool Method ----downloadImageWithRequest:(NSURLRequest *)request{ if  { dispatch_async_main_safe{ [[NSNotificationCenter defaultCenter] postNotificationName:DWWebImageDownloadFinishNotification object:nil userInfo:@{@"error":DWErrorWithDescription(10002,@"无法生成request对象"),@"url":self.url}]; })); return; } self.url = request.URL.absoluteString; self.task = [self.session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if  {///下载错误 dispatch_async_main_safe{ [[NSNotificationCenter defaultCenter] postNotificationName:DWWebImageDownloadFinishNotification object:nil userInfo:@{@"error":DWErrorWithDescription(10003, @"任务取消或错误"),@"url":self.url}]; })); return ; } _session = nil; UIImage * image = [UIImage imageWithData:data]; self.downloadFinish = YES;///标志下载完成 self.image = image; if  { dispatch_async_main_safe{ [[NSNotificationCenter defaultCenter] postNotificationName:DWWebImageDownloadFinishNotification object:nil userInfo:@{@"error":DWErrorWithDescription(10000, ([NSString stringWithFormat:@"图片下载失败:%@",self.url])),@"url":self.url}]; })); return ; } //保存数据 [[DWWebImageCache shareCache] cacheObj:data forKey:self.url]; ///并发遍历 [self.callBacks enumerateObjectsWithOptions:(NSEnumerationConcurrent | NSEnumerationReverse) usingBlock:^(DWWebImageCallBack _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if  { //图片回调 dispatch_async_main_safe{ obj; }); } }]; ///发送通知 dispatch_async_main_safe{ [[NSNotificationCenter defaultCenter] postNotificationName:DWWebImageDownloadFinishNotification object:nil userInfo:@{@"url":self.url,@"image":image}]; })); }];}-(NSMutableArray<DWWebImageCallBack> *)callBacks{ if (!_callBacks) { _callBacks = [NSMutableArray array]; } return _callBacks;}@end

图形缓存类

SD中对图纸展开了不知凡几缓存,包蕴内部存款和储蓄器缓存和磁盘缓存。这里我们也来模拟一下其促成进度。对于这么些缓存类,大家能够给谐和提多少个必要:

1.扶助内部存款和储蓄器缓存及磁盘缓存二种缓存格局

2.对于缓存类缓存文件应做加密

3.磁盘缓存应封存清除缓存接口,并且应具备过期缓存自动清除功能

对本身好一些,少提一些需求吗┑┍

就此依照要求大家得以大要知道几个技能点,一一深入分析一下。

此间大家接纳的内部存储器缓存是系统提供的NSCache类。

NSCache基本使用格局与字典同样,以key值存值和取值。不一样的是,NSCache会在内部存款和储蓄器吃紧的时候自动释放内存。且相对于字典来讲,NSCache是线程安全的,所以您并无需手动加锁哦。

故而鲜明了内部存款和储蓄器缓存的落真实情局势后,我们借使计划缓存逻辑就能够。

小编们明白,内存读取速度是要大于磁盘读取速度的,所以当去缓存的时候大家先行取内部存款和储蓄器缓存使大家的非常重要战略。别的举行磁盘缓存的时候大家还要注意两点,第一点,必供给异步子线程去推行,那样能够制止线程阻塞。第二点,既然开启了子线程就活该潜心线程安全,所以那边应留心加线程安全相关的代码。

图片 9缓存读写

此地大家利用与SDWebImage相同的做法,以图表下载UOdysseyL做MD5加密后的字符串当做key与缓存一一对应。加密算法相对稳固,再次不做赘述,稍后会有联合放代码。

活动清理的核情感想则是每当第一回加载大家的Api的时等候检查验大家的磁盘缓存文件的末段修改时间,借使距离当前抢先大家预设的逾期时间则将文件移除。

图片 10移除过期文件

上边是图形缓存类的代码

#pragma mark --- 缓存管理类 ---@interface DWWebImageCache : NSObject<NSCopying>///缓存策略@property (nonatomic ,assign) DWWebImageCachePolicy cachePolicy;///缓存数据类型@property (nonatomic ,assign) DWWebImageCacheType cacheType;///缓存过期时间,默认值7天@property (nonatomic ,assign) unsigned long long expirateTime;///是否加密缓存@property (nonatomic ,assign) BOOL useSecureKey;///缓存空间@property (nonatomic ,copy) NSString * cacheSpace;///单例+(instancetype)shareCache;///通过key存缓存-cacheObj:obj forKey:(NSString *)key;///通过key取缓存-objCacheForKey:(NSString *)key;///通过key移除缓存-removeCacheByKey:(NSString *)key;///移除过期缓存-removeExpiratedCache;@end

#pragma mark --- DWWebImageCache ---@interface DWWebImageCache ()@property (nonatomic ,strong) NSCache * memCache;@property (nonatomic ,strong) dispatch_semaphore_t semaphore;@property (nonatomic ,strong) NSFileManager * fileMgr;@end@implementation DWWebImageCache#pragma mark --- 接口方法 ----(instancetype)init{ self = [super init]; if  { _memCache = [[NSCache alloc] init]; _memCache.totalCostLimit = DWWebImageCacheDefaultCost; _memCache.countLimit = 20; _expirateTime = DWWebImageCacheDefaultExpirateTime; _useSecureKey = YES; _cachePolicy = DWWebImageCachePolicyDisk; _cacheType = DWWebImageCacheTypeData; _semaphore = dispatch_semaphore_create; _fileMgr = [NSFileManager defaultManager]; [self createTempPath]; } return self;}-cacheObj:obj forKey:(NSString *)key{ NSString * url = key; key = transferKey(key, self.useSecureKey); if (self.cachePolicy & DWWebImageCachePolicyDisk) {///磁盘缓存 writeFileWithKey(obj, url, key, self.semaphore, self.fileMgr,self.cacheSpace); } if (self.cachePolicy & DWWebImageCachePolicyMemory) { ///做内存缓存 [self.memCache setObject:obj forKey:key cost:costForObj]; }}-objCacheForKey:(NSString *)key{ __block id obj = nil; key = transferKey(key, self.useSecureKey); obj = [self.memCache objectForKey:key]; if  { NSAssert((self.cacheType != DWWebImageCacheTypeUndefined), @"you must set a cacheType but not DWWebImageCacheTypeUndefined"); readFileWithKey(key, self.cacheType, self.semaphore, self.cacheSpace,^(id object) { obj = object; }); } return obj;}-removeCacheByKey:(NSString *)key{ key = transferKey(key, self.useSecureKey); [self.memCache removeObjectForKey:key]; [self.fileMgr removeItemAtPath:objPathWithKey(key,self.cacheSpace) error:nil];}-removeExpiratedCache{ if (self.expirateTime) { dispatch_async(dispatch_get_global_queue, ^{ NSDirectoryEnumerator *dir=[self.fileMgr enumeratorAtPath:sandBoxPath(self.cacheSpace)]; NSString *path=[NSString new]; unsigned long long timeStamp = [[NSDate date] timeIntervalSince1970]; while ((path=[dir nextObject])!=nil) { NSString * fileP = objPathWithKey(path,self.cacheSpace); NSDictionary * attrs = [self.fileMgr attributesOfItemAtPath:fileP error:nil]; NSDate * dataCreate = attrs[NSFileModificationDate]; if ((timeStamp - [dataCreate timeIntervalSince1970]) > self.expirateTime) { [self.fileMgr removeItemAtPath:fileP error:nil]; } } }); }}#pragma mark -- Tool Method ----createTempPath{ if (![self.fileMgr fileExistsAtPath:sandBoxPath(self.cacheSpace)]) { [self.fileMgr createDirectoryAtPath:sandBoxPath(self.cacheSpace) withIntermediateDirectories:YES attributes:nil error:NULL]; }}#pragma mark --- Setter、getter ----setExpirateTime:(unsigned long long)expirateTime{ _expirateTime = expirateTime; if (expirateTime) { [self removeExpiratedCache]; }}-(NSString *)cacheSpace{ if (!_cacheSpace) { return @"defaultCacheSpace"; } return _cacheSpace;}#pragma mark --- 单例 ---static DWWebImageCache * cache = nil;+(instancetype)shareCache{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ cache = [[self alloc] init]; }); return cache;}+(instancetype)allocWithZone:(struct _NSZone *)zone{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ cache = [super allocWithZone:zone]; }); return cache;}-copyWithZone:zone{ return cache;}#pragma mark --- 内联函数 ---/** 异步文件写入 @param obj 写入对象 @param url 下载url @param key 缓存key @param semaphore 信号量 @param fileMgr 文件管理者 @param cacheSpace 缓存空间 */static inline void writeFileWithKey(id obj,NSString * url,NSString * key,dispatch_semaphore_t semaphore,NSFileManager * fileMgr,NSString * cacheSpace){ dispatch_async(dispatch_get_global_queue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSString * path = objPathWithKey(key,cacheSpace); if ([fileMgr fileExistsAtPath:path]) { [fileMgr removeItemAtPath:path error:nil]; } if ([obj2Data writeToFile:path atomically:YES]) { dispatch_async_main_safe{ [[NSNotificationCenter defaultCenter] postNotificationName: DWWebImageCacheCompleteNotification object:nil userInfo:@{@"url":url}]; }); } dispatch_semaphore_signal(semaphore); });};/** 文件读取 @param key 缓存key @param type 文件类型 @param semaphore 信号量 @param cacheSpace 缓存空间 @param completion 读取完成回调 */static inline void readFileWithKey(NSString * key,DWWebImageCacheType type,dispatch_semaphore_t semaphore,NSString * cacheSpace,void (^completion){ dispatch_sync(dispatch_get_global_queue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSData * data = [NSData dataWithContentsOfFile:objPathWithKey(key,cacheSpace)]; if (data && completion) { completion(transferDataToObj(data, type)); } dispatch_semaphore_signal(semaphore); });};/** 数据格式转换 @param data 源数据 @param type 数据类型 @return 转换后数据 */static inline id transferDataToObj(NSData * data,DWWebImageCacheType type){ switch  { case DWWebImageCacheTypeData: return data; break; case DWWebImageCacheTypeImage: return [UIImage imageWithData:data]; break; default: return nil; break; }};/** 返回文件路径 @param key 缓存key @param cacheSpace 缓存空间 @return 文件路径 */static inline NSString * objPathWithKey(NSString * key,NSString * cacheSpace){ return [NSString stringWithFormat:@"%@/%@",sandBoxPath(cacheSpace),key];};/** 对象转为NSData @param obj 对象 @return 转换后data */static inline NSData * obj2Data{ NSData * data = nil; if ([obj isKindOfClass:[NSData class]]) { data = obj; } else if([obj isKindOfClass:[UIImage class]]) { data = UIImageJPEGRepresentation; } return data;}/** 沙盒路径 @param cacheSpace 缓存空间 @return 沙盒路径 */static inline NSString * sandBoxPath(NSString * cacheSpace){ return [NSHomeDirectory() stringByAppendingString:[NSString stringWithFormat:@"/Documents/DWWebImageCache/%@/",cacheSpace]];};/** 计算对象所需缓存成本 @param obj 对象 @return 缓存成本 */static inline NSUInteger costForObj{ NSUInteger cost = 0; ///根据数据类型计算cost if ([obj isKindOfClass:[NSData class]]) { cost = [[obj valueForKey:@"length"] unsignedIntegerValue]; } else if ([obj isKindOfClass:[UIImage class]]) { UIImage * image = (UIImage *)obj; cost = (NSUInteger)image.size.width * image.size.height * image.scale * image.scale; } return cost;};/** 返回缓存key @param originKey 原始key @param useSecureKey 是否加密 @return 缓存key */static inline NSString * transferKey(NSString * originKey,BOOL useSecureKey){ return useSecureKey?encryptToMD5(originKey):originKey;};/** 返回MD5加密字符串 @param str 原始字符串 @return 加密后字符串 */static inline NSString *encryptToMD5(NSString * str){ CC_MD5_CTX md5; CC_MD5_Init ; CC_MD5_Update (&md5, [str UTF8String], [str length]); unsigned char digest[CC_MD5_DIGEST_LENGTH]; CC_MD5_Final (digest, &md5); return [NSString stringWithFormat: @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7], digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]];};@end

有未有觉察分模块后思路很清晰,大家随后给协和捋捋需要吗。

  • 我们的管理类要能区分当前UENCOREL存在缓存的话,大家不要求展开下载任务,直接从缓存中读取。
  • 假使未有缓存,判别当前UPRADOL是或不是正在下载,要是正在下载不应开启新的下载任务,而是为此前的天职增添回调。
  • 应当为任务增加优先级,新增的下载职分应该较在此之前增多且尚未早先的下载职责具有越来越高的开始的一段时期级。

前两个供给,无非正是四个规范判定,而职分优先级我们能够因此NSOperation去丰硕依赖,进而完毕。大家清楚NSOperation和NSU奇骏LSessionTask都以内需手动开启的,所以大家得以重写NSOperation的resume方法,能够何况开启下载职分。

并且我们掌握丰裕到NSOperationQueue中的NSOperation会按需自行调用resume方法,所以大家能够成功的借助NSOperationQueue完结我们下载职分的相互重视关系。看一下代码:

图片 11为下载职分增添注重

只怕今后那样说依旧不懂,先等下,接着看。

图片 12下载逻辑

咱俩看看,每叁次当创制新的义务时,我都会将上次记下的天职的信赖性设置为新的天职,那样新添长的义务就能事先于上两个义务试行。然后将它参预到行列中,那样就能活动开启职分。

管理类和线程类的漫天代码放一下:

#pragma mark --- 任务线程类 ---@interface DWWebImageOperation : NSOperation///图片下载器@property (nonatomic ,strong) DWWebImageDownloader * donwloader;///下载任务是否完成@property (nonatomic , assign, getter=isFinished) BOOL finished;///以url及session下载图片-(instancetype)initWithUrl:(NSString *)url session:(NSURLSession *)session;@end#pragma mark --- 下载管理类 ---@interface DWWebImageManager : NSObject<NSCopying>///线程字典/** url为key,对应任务线程 */@property (nonatomic ,strong) NSMutableDictionary <NSString *,DWWebImageOperation *>* operations;///缓存管理对象@property (nonatomic ,strong) DWWebImageCache * cache;///单例+(instancetype)shareManager;///以url下载图片,进行回调-downloadImageWithUrl:(NSString *)url completion:(DWWebImageCallBack)completion;///以url移除下载任务-removeOperationByUrl:(NSString *)url;@end

#pragma mark --- DWWebImageOperation ---@implementation DWWebImageOperation@synthesize finished = _finished;-(instancetype)initWithUrl:(NSString *)url session:(NSURLSession *)session{ self = [super init]; if  { _donwloader = [[DWWebImageDownloader alloc] initWithSession:session]; [_donwloader downloadImageWithUrlString:url]; } return self;}-start{ [super start]; [self.donwloader resume];}-cancel{ [super cancel]; [self.donwloader cancel];}-setFinished:finished { [self willChangeValueForKey:@"isFinished"]; _finished = finished; [self didChangeValueForKey:@"isFinished"];}@end#pragma mark --- DWWebImageManager ---@interface DWWebImageManager ()@property (nonatomic ,strong) NSURLSession * session;@property (nonatomic ,strong) dispatch_semaphore_t semaphore;@property (nonatomic ,strong) NSOperationQueue * queue;@property (nonatomic ,strong) DWWebImageOperation * lastOperation;@end@implementation DWWebImageManager-(instancetype)init{ self = [super init]; if  { self.semaphore = dispatch_semaphore_create; self.cache = [DWWebImageCache shareCache]; self.cache.cachePolicy = DWWebImageCachePolicyDisk | DWWebImageCachePolicyMemory; [self.cache removeExpiratedCache]; dispatch_async_main_safe{ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cacheCompleteFinishNotice:) name:DWWebImageCacheCompleteNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadFinishNotice:) name:DWWebImageDownloadFinishNotification object:nil]; }); } return self;}///下载图片-downloadImageWithUrl:(NSString *)url completion:(DWWebImageCallBack)completion{ NSAssert(url.length, @"url不能为空"); dispatch_async(dispatch_get_global_queue, ^{ ///从缓存加载图片 UIImage * image = [UIImage imageWithData:[self.cache objCacheForKey:url]]; if  { dispatch_async_main_safe{ completion; }); } else {///无缓存 dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); DWWebImageOperation * operation = self.operations[url];///取出下载任务 if (!operation) {///无任务 operation = [[DWWebImageOperation alloc] initWithUrl:url session:self.session]; self.operations[url] = operation; if (self.lastOperation) { [self.lastOperation addDependency:operation]; } [self.queue addOperation:operation]; self.lastOperation = operation; } if (!operation.donwloader.downloadFinish) { [operation.donwloader.callBacks addObject:[completion copy]]; } else { ///从缓存读取图片回调 dispatch_async_main_safe{ completion(operation.donwloader.image); }); } dispatch_semaphore_signal(self.semaphore); } });}///下载完成回调-downloadFinishNotice:(NSNotification *)sender{ NSError * error = sender.userInfo[@"error"]; if  {///移除任务 [self removeOperationByUrl:sender.userInfo[@"url"]]; [self removeCacheByUrl:sender.userInfo[@"url"]]; } else { NSString * url = sender.userInfo[@"url"]; DWWebImageOperation * operation = self.operations[url];///取出下载任务 operation.finished = YES; }}///缓存完成通知回调-cacheCompleteFinishNotice:(NSNotification *)sender{ NSString * url = sender.userInfo[@"url"]; if (url.length) { [self removeOperationByUrl:sender.userInfo[@"url"]]; }}///移除下载进程-removeOperationByUrl:(NSString *)url{ DWWebImageOperation * operation = self.operations[url]; [operation cancel]; [self.operations removeObjectForKey:url];}///移除缓存-removeCacheByUrl:(NSString *)url{ [self.cache removeCacheByKey:url];}-(NSMutableDictionary<NSString *,DWWebImageOperation *> *)operations{ if (!_operations) { _operations = [NSMutableDictionary dictionary]; } return _operations;}-(NSURLSession *)session{ if (!_session) { NSURLSessionConfiguration * config = [NSURLSessionConfiguration defaultSessionConfiguration]; config.timeoutIntervalForRequest = 15; _session = [NSURLSession sessionWithConfiguration:config]; } return _session;}-(NSOperationQueue *)queue{ if  { _queue = [[NSOperationQueue alloc] init]; _queue.maxConcurrentOperationCount = 6; } return _queue;}#pragma mark --- 单例 ---static DWWebImageManager * mgr = nil;+(instancetype)shareManager{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ mgr = [[self alloc] init]; }); return mgr;}+(instancetype)allocWithZone:(struct _NSZone *)zone{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ mgr = [super allocWithZone:zone]; }); return mgr;}-copyWithZone:zone{ return mgr;}@end

由来,你早已和睦实现了三个异步下载类。你能够像SD同样,为UIImageView、UIButton等充足分类达成平等的效能。

本条下载思路与SD千篇一律,相信您和睦撸一份将来对SD会有更深的接头。当然SD为大家做的远不仅那些,你怎么大概凭自己一个人的力量抗衡千人。有空多读读成熟的第三方代码也是对自家的练习与升迁。

一律的,老鸟把写好的下载类同样位于了笔者的Git上,在此地。

参考资料

  • NSURLSessionConfiguration API详解

  • NSUENVISIONLSession使用表达及后台专业流程分析

  • SDWebImage

你说老开车员今日怎么不逗比了,人家平昔是治学严厉的老学究好么!

图片 13傲娇

恩,你在忍忍,那应当是自己更新前最终一遍做软广了=。=

DWCoreTextLabel更新到后天早已1.1.6版本了,现在除了图像和文字混排作用,还帮衬文件类型的自动物检疫查测量试验,异步绘制减弱系统的卡顿,异步加载并缓存图片的职能。

version 1.1.0周密补助电动链接帮忙、定制检查评定法规、图像和文字混排、响应事件优化大多数算法,提升响应作用及绘制功能

version 1.1.1高亮取消逻辑优化自动物检疫查实验逻辑优化部分常用方法改为内联函数,提升运转效能

version 1.1.2制图逻辑优化,改为异步绘制(源码修改自YYTextAsyncLayer)

version 1.1.3异步绘制改变完毕、去除事务管理类,事务管理类仍可校订,进行中

version 1.1.4事务管理类去除,异步绘制文件收取

version 1.1.5增多网络图片异步加载库,帮助绘制网络图片

图片 14DWCoreTextLabel

安排图片、绘制图片、增加事变统统一句话完毕~

图片 15一句话完毕

不遗余力保持系统Label属性让您可以无缝过渡使用~

图片 16无缝过渡

恩,说了这么多,老手放一下地点:DWCoreTextLabel,婴孩们给个star吧爱您啊

图片 17爱你哟

本文由永利402游戏网站-永利402com官方网站发布于网络编程,转载请注明出处:老驾车员出品———疯狂造轮子之图片异步下载

关键词:

上一篇:学学自定义流水布局

下一篇:没有了