一、SDWebImage第三方框架
1、是什么
一个为UIImageView提供一个分类来支持远程服务器图片加载的库。
功能:
1、一个添加了web图片加载和缓存管理的UIImageView分类
2、一个异步图片下载器
3、一个异步的内存加磁盘综合存储图片并且自动处理过期图片
4、支持动态gif图
5、支持webP格式的图片
6、后台图片解压处理
7、确保同样的图片url不会下载多次
8、确保伪造的图片url不会重复尝试下载
9、确保主线程不会阻塞
2、简单使用
#import "UIImageView+WebCache.h"
UIImageView *image = [[UIImageView alloc] init];
[image sd_setImageWithURL:[NSURL URLWithString:@"图片地址"] placeholderImage:[UIImage imageNamed:@"站位图"]];
3、工作流程
1、入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。
2、进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:.
3、先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
4、SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。
5、如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。
6、根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。
7、如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。
8、如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。
9、共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
10、图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。
11、connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
12、图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
13、在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
14、通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
15、SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
16、SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
17、SDWebImagePrefetcher 可以预先下载图片,方便后续使用
参考:
[SDWebImage原理](https://www.jianshu.com/p/ff9095de1753?utm_source=desktop&utm_medium=timelin
二、死锁问题
1、什么是死锁?
线程卡住,不会往下执行
2、什么情况下会产生死锁?
使用sync函数往当前串行队列中添加任务,会卡住当前的串行对列,产生死锁,其他的基本不会。
- 主队列的特点:
先执行完主线程上的代码,才会执行主对列中的任务 - dispatch_sync的特点
立马在当前线程执行任务
NSLog(@"任务1");
//对列的特点:排队,FIFO,先进先出
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务2");
});
NSLog(@"任务2");
上面这段代码会崩掉,
因为根据主队列和dispatch_sync的特点,任务2和任务3会造成相互等待而不能动的情况,产生死锁。
三、相关面试题
1、你理解的多线程?
2、下面打印的结果是什么?
dispatch_queue_t queue = dispatch_queue_create("hahaha", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任务1==%@",[NSThread currentThread]);
[self performSelector:@selector(Test) withObject:nil afterDelay:.0];
NSLog(@"任务3==%@",[NSThread currentThread]);
});
-(void)Test{
NSLog(@"任务2==%@",[NSThread currentThread]);
}
2021-09-16 16:34:00.814177+0800 OCStudy[32750:3081015] 任务1==<NSThread: 0x6000009dda00>{number = 6, name = (null)}
2021-09-16 16:34:00.814845+0800 OCStudy[32750:3081015] 任务3==<NSThread: 0x6000009dda00>{number = 6, name = (null)}
结果只打印了1和3。
[self performSelector:@selector(Test) withObject:nil afterDelay:.0];
换成
[self performSelector:@selector(Test) withObject:nil]; //等价于 [self Test];
结果只打印1、2、3。
原因:
[self performSelector:@selector(Test) withObject:nil afterDelay:.0]
这句代码本质是往runloop里面添加了定时器,定时器只有在runloop中才能起作用,而子线程默认是没有启动runloop的,所以不起作用。
dispatch_async(queue, ^{
NSLog(@"任务1==%@",[NSThread currentThread]);
[self performSelector:@selector(Test) withObject:nil afterDelay:.2];
NSLog(@"任务3==%@",[NSThread currentThread]);
//添加runloop
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
添加runloop即可。
网友评论