- 1、取消当前的下载操作 如果不取消,那么当tableView滑动的时候,当前cell的imageView会一直去下载图片,然后优先显示下载完成的图片,直接错乱。保存本次operation,如果发生多次图片请求可以用来取消,先取消当前UIImageView正在下载的任务,然后在保存operations。
[self sd_cancelCurrentImageLoad];
-
2、关键类 SDWebImageManager(单例) 来处理图片下载
-
3、SDImageCache 专门管理缓存,NSCache负责内存缓存,用法和NSDictionary基本一样,磁盘缓存用NSFileManager写文件的方式完成。
- 1、NSCache具有自动删除的功能,以减少系统占用的内存,还能设置内存临界值
- 2、NSCache是线程安全的,不需要加线程锁
-
4、每次向SDWebImageCache索取图片的时候,会先根据图片URL对应的key值先检查内存中是否有对应的图片,如果有则直接返回;
-
5、如果没有则在ioQueue中去硬盘中查找,其中文件名是是根据URL生成的MD5值,找到之后先将图片缓存在内存中,然后在把图片返回:
-
6、创建自动释放池,内存即时释放。如果你的应用程序或者线程是要长期运行的并且有可能产生大量autoreleased对象, 你应该使用autorelease pool blocks
@autoreleasepool {
}
- 7、开了异步串行队列去Disk中查找,保证不阻塞主线程,而且开了autoreleasepool以降低内存暴涨问题,能得到及时释放,如果能取到,首先缓存到内存中然后再回调
- 8、如果内存和磁盘中都取不到图片,就会让Manager的另一个手下SDWebImageDownloader (单例)去下载图片,专门负责图片的下载图片的下载都是放在NSOperationQueue中完成的
- 9、防止循环引用,weak的这里设置成strong 避免被释放掉了
__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf) strongSelf = weakSelf;
- 10、SDWebImageDownloaderOperation派生自NSOperation,负责图片下载工作
-
11、NSOperation Queue 增加一个对象 ,如果不是FIFO 是 LIFO 队列设置依赖,后进来的成为上面的依赖
-
12、来确保同一url只会下载一次,dispatch_barrier_sync操作来保证同一时间只有一个线程能对URLCallbacks进行操作。一直等到这个 queue中排在它前面的任务都执行完成后才会开始执行自己,自己执行完毕后,再会取消阻塞,使这个 queue中排在它后面的任务继续执行。
-
13、通过继承NSOperation的SDWebImageDownloaderOperation进来初始化下载任务
- 1、必须重写Start的方法
- 2、把NSURLConnection替换成了NSURLSession来进行网络请求的操作
-
14、SDWebImageCache的清理缓存策略
- 1、收到内存警告,清楚NSCache [self.memCache removeAllObject]
- 2、程序关闭时对硬盘文件做一些处理
- 3、程序进入后台是也对硬盘进行一些读写,
- 4、根据缓存策略来清理磁盘缓存,然后先遍历所有缓存文件,记录过期的文件,计算缓存总文件大小,先删除过期的文件(默认一周),如果设置最大缓存,而且已经缓存的文件大小超过这个预期值,把所有的文件按最后编辑的时间升序,然后一个个删除,当缓存低于临界就break。
- 5、以设置的最大缓存大小的一半作为清理目标 ,从小到大排序,也就是最早的时间在最前面,按照最后修改时间来排序剩下的缓存文件
- 6、删除文件,直到缓存总大小降到我们期望的大小
知识点整理:
1.这里的Dispatch_barrier_async来确保线程安全操作,任务需要等待之前的任务执行完
2.强大的Block回调和Associated Objects — runtime
3.NSOperationQueue和NSOperation的,相对于GCD来说,他能取消操作,也能设置任务之间的依赖,相较于GCD来说更加的强大,AF也是基于这个玩的,这样才可以完成LIFO的队列需求
4.NSCache和NSFileManager的二级缓存操作
5.缓存策略:超过一星期的杀掉,还能设置最大缓存数,根据文件的最后编辑时间进行升序,然后一个个删除,低于临界的时候清理完成
6.异步操作图片的处理,缩放和解压操作,还有图片的类型处理,这东西有空再研究,很少接触
7.还有就是同一URL不会被下载的优化处理
8.最后还是觉得封装和任务的分配都非常的清晰,值得学习
知识点:
- 1、接口通过分类实现,一个简单的接口将其中复杂的实现细节全部隐藏:简单就是美。
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
- 2、NSOperationQueue和NSOperation的,相对于GCD来说,他能取消操作,也能设置任务之间的依赖,相较于GCD来说更加的强大,AF也是基于这个玩的,这样才可以完成LIFO的队列需求,设置最大并发数6
- 3、NSCache和NSFileManager的二级缓存操作
- 4、缓存策略:超过一星期的杀掉,还能设置最大缓存数,根据文件的最后编辑时间进行升序,然后一个个删除,低于临界的时候清理完成
- 5、异步操作图片的处理,缩放和解压操作
- 6、同一URL不会被下载的优化处理
- 7、必须先取消之前的下载任务 如果不取消,那么当tableView滑动的时候,当前cell的imageView会一直去下载图片,然后优先显示下载完成的图片,直接错乱
- 8、@autoreleasepool 减少内存峰值 当自动释放池被销毁时,会向自动释放池中的所有对象发送release消息,释放自动释放池中的所有对象。
- 9、SDImageCache初始化的时候注册了几个通知,当内存警告的时候,程序进入后台或者程序杀死的时候根据策略清理缓存。
- 10、内存警告:自动清除NSCache内存缓存,进入后台和程序杀死:清理过期的文件(默认一周),然后有个缓存期望值,对比已有文件的大小,先根据文件最后编辑时间升序排,把大于期望值大小的文件全部杀掉
- 11、下载任务开始是用NSURLSession了,不用NSURLConnetion了,由于SD是自定义的NSOperation
内部需要重写start方法,在该方法里面配置Session,当taskResume的时候,根据设置的代理就能取到不同的回调参数
didReceiveResponse能获取到响应的所有参数规格,例如总size
didReceiveData是一步步获取data,压缩解码回调progressBlock
didCompleteWithError全部完成回调,图片解码,回调completeBlock - 12、下载用NSOperation和NSOperationQueue来进行,SD派生了一个SDWebImageDownloaderOperation负责图片的下载任务,调用
initWithRequest:inSession:options:progress:completed:cancelled: - 13、SDImageCache内存缓存用的是NSCache,Disk缓存用的是NSFileManager的文件写入操作,那么查看缓存的时候是先去内存查找,这里的key都是经过MD5之后的字串,找到直接回调,没找到继续去磁盘查找,开异步串行队列去找,避免卡死主线程,启用autoreleasepool避免内存暴涨,查到了缓存到内存,然后回调
- 14、dispatch_barrier_sync:前面的任务执行结束后它才执行,而且它后面的任务要等它执行完成之后才会执行
- 15、使用weak self strong self 防止retain circle
__weak __typeof(self) weakSeld_AT = self;
// 代码段里面的self不被释放。
__weak typedef(weakSelf) strongSelf = weakSelf;
图片解压
- 要理解 iOS 中图片的解压缩并不难,重点是要理解位图的概念。而图片解压缩的过程其实就是将图片的二进制数据转换成像素数据的过程。
- 位图就是一个像素数组,数组中的每个像素就代表着图片中的一个点
- 不管是 JPEG 还是 PNG 图片,都是一种压缩的位图图形格式。
- 只不过 PNG 图片是无损压缩,并且支持 alpha 通道,而 JPEG 图片则是有损压缩,可以指定 0-100% 的压缩比。值得一提的是,在苹果的 SDK 中专门提供了两个函数用来生成 PNG 和 JPEG 图片
UIKIT_EXTERN NSData * __nullable UIImagePNGRepresentation(UIImage * __nonnull image);
// return image as JPEG. May return nil if image has no CGImageRef or invalid bitmap format. compression is 0(most)..1(least)
UIKIT_EXTERN NSData * __nullable UIImageJPEGRepresentation(UIImage * __nonnull image, CGFloat compressionQuality);
- 在将磁盘中的图片渲染到屏幕之前,必须先要得到图片的原始像素数据,才能执行后续的绘制操作,这就是为什么需要对图片解压缩的原因。
- 二进制数据转换成像素数据
- 当未解压缩的图片将要渲染到屏幕时,系统会在主线程对图片进行解压缩,而如果图片已经解压缩了,系统就不会再对图片进行解压缩。
- 而强制解压缩的原理就是对图片进行重新绘制,得到一张新的解压缩后的位图。其中,用到的最核心的函数是 CGBitmapContextCreate :
- 我们提到了图片的解压缩是一个非常耗时的 CPU 操作,并且它默认是在主线程中执行的。那么当需要加载的图片比较多时,就会对我们应用的响应性造成严重的影响,尤其是在快速滑动的列表上,这个问题会表现得更加突出。
- 放在一个IO串行队列里面解压图片
自定义并发的NSOperation
需要以下步骤:
- 1.start方法:该方法必须实现,
- 2.main:该方法可选,如果你在start方法中定义了你的任务,则这个方法就可以不实现,但通常为了代码逻辑清晰,通常会在该方法中定义自己的任务
- 3.isExecuting isFinished 主要作用是在线程状态改变时,产生适当的KVO通知
- 4.isConcurrent :必须覆盖并返回YES;
- 当一个 operation 开始执行后,它会一直执行它的任务直到完成或被取消为止。我们可以在任意时间点取消一个 operation ,甚至是在它还未开始执行之前。
- 为了让我们自定义的 operation 能够支持取消事件,我们需要在代码中定期地检查 isCancelled 方法的返回值,一旦检查到这个方法返回 YES ,我们就需要立即停止执行接下来的任务。根据苹果官方的说法,isCancelled 方法本身是足够轻量的,所以就算是频繁地调用它也不会给系统带来太大的负担。
当我们自定义一个 operation 类时,我们需要考虑在以下几个关键点检查 isCancelled 方法的返回值:
- 在真正开始执行任务之前;
- 至少在每次循环中检查一次,而如果一次循环的时间本身就比较长的话,则需要检查得更加频繁;
- 在任何相对来说比较容易中止 operation 的地方。
/// 支持取消操作
- (void)main {
@try {
if (self.isCancelled) return;
NSLog(@"Start executing %@ with data: %@, mainThread: %@, currentThread: %@", NSStringFromSelector(_cmd), self.data, [NSThread mainThread], [NSThread currentThread]);
for (NSUInteger i = 0; i < 3; i++) {
if (self.isCancelled) return;
sleep(1);
NSLog(@"Loop %@", @(i + 1));
}
NSLog(@"Finish executing %@", NSStringFromSelector(_cmd));
}
@catch(NSException *exception) {
NSLog(@"Exception: %@", exception);
}
}
在默认情况下,operation 是同步执行的,也就是说在调用它的 start 方法的线程中执行它们的任务。而在 operation 和 operation queue 结合使用时,operation queue 可以为非并发的 operation 提供线程,因此,大部分的 operation 仍然可以异步执行。
但是,如果你想要手动地执行一个 operation ,又想这个 operation 能够异步执行的话,你需要做一些额外的配置来让你的 operation 支持并发执行。下面列举了一些你可能需要重写的方法:
-
start :必须的,所有并发执行的 operation 都必须要重写这个方法,替换掉 NSOperation 类中的默认实现。start 方法是一个 operation 的起点,我们可以在这里配置任务执行的线程或者一些其它的执行环境。另外,需要特别注意的是,在我们重写的 start 方法中一定不要调用父类的实现;
-
main :可选的,通常这个方法就是专门用来实现与该 operation 相关联的任务的。尽管我们可以直接在 start 方法中执行我们的任务,但是用 main 方法来实现我们的任务可以使设置代码和任务代码得到分离,从而使 operation 的结构更清晰;
-
isExecuting 和 isFinished :必须的,并发执行的 operation 需要负责配置它们的执行环境,并且向外界客户报告执行环境的状态。因此,一个并发执行的 operation 必须要维护一些状态信息,用来记录它的任务是否正在执行,是否已经完成执行等。此外,当这两个方法所代表的值发生变化时,我们需要生成相应的 KVO 通知,以便外界能够观察到这些状态的变化;
-
isConcurrent :必须的,这个方法的返回值用来标识一个 operation 是否是并发的 operation ,我们需要重写这个方法并返回 YES 。
@implementation OQConcurrentOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
- (id)init {
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
}
return self;
}
- (BOOL)isConcurrent {
return YES;
}
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
@end
这一部分的代码看上去比较简单,但是却需要我们用心地去理解它。首先,我们用 @synthesize 关键字手动合成了两个实例变量 _executing 和 _finished ,然后分别在重写的 isExecuting 和 isFinished 方法中返回了这两个实例变量。另外,我们通过查看 NSOperation 类的头文件可以发现,executing 和 finished 属性都被声明成了只读的 readonly 。所以我们在 NSOperation 子类中就没有办法直接通过 setter 方法来自动触发 KVO 通知,这也是为什么我们需要在接下来的代码中手动触发 KVO 通知的原因。
网友评论