iOS 异步加载本地图片
iOS 异步加载本地图片
问题
当某个界面使用系统API + (nullable UIImage *)imageNamed:(NSString *)name;
加载了过多本地图片资源时,不可避免的会产生卡顿感。
有过instrument
中Time Profiler
经验的同学应该有数据上的直观体验,调用imageNamed:
首次加载某张图片,往往是最耗时的。
- I/O操作:首次加载本地图片时,内存中是没有的,因此去磁盘中加载图片。
- 解码:解码图片,将位图数据还原成原始像素数据。
以上两步都是imageNamed:
方法在底层做的耗时操作。知道原因,那么方案也就有了,将以上操作放入到子线程中。
方案1
有些同学可能想,在子线程调用imageNamed:
先提前加载一次图片,并且系统会帮我们缓存,省事省力省心,美滋滋~但是这里有两个坑:
-
线程安全问题,该方法在iOS9以后才线程安全,也就是说iOS9以下版本使用有风险;
15486648779770.jpg -
效率问题,将
imageNamed:
方法放入到子线程调用,使用Time Profiler
观察会发现,该方法在子线程耗时比在主线程久的多,猜测为了保证线程安全,底层很多加锁的操作导致;
方案2
那么美滋滋的不行,只能绕一点远路了;
加载图片
// I/O将磁盘数据加载入内存中,NSData类型
+ (nullable instancetype)dataWithContentsOfFile:(NSString *)path;
// 将NSData类型的图片数据转为UIImage类型
- (nullable instancetype)initWithData:(NSData *)data scale:(CGFloat)scale NS_AVAILABLE_IOS(6_0);
解码图片
站在前人肩膀上敲代码很方便,如果你的项目中使用了SDWebImage或YYImage的话,可以直接调用它们框架内部的解码图片方法;
YYImageDecoder类
- (instancetype)imageByDecoded;
SDWebImageDecoder类
+ (UIImage *)decodedImageWithImage:(UIImage *)image;
它们内部逻辑相似:获取imageA
对象位图的信息(透明通道、缩放系数、宽高),创建渲染的上下文(CGBitmapContextCreate
),将位图渲染在上下文中(CGContextDrawImage
),在上下文中提取带有位图信息的imageB
对象(CGBitmapContextCreateImage
);
缓存
这样子系统就不会帮我们缓存图片了,因为需要我们自己管理缓存图片。
使用YYThreadSafeDictionary
线程安全的容器,子线程保存下解码后的图片。
以上几步操作都是在子线程中,降低了主线程的占用,可以有效的解决由于加载本地图片造成的卡顿问题。当在子线程处理完图片后,我们再切换到主线程,将图片扔给使用方。
// 调用接口大致这样
+ (void)asyncLoadImgWithimgName:(NSString *)imgName block:(void(^)(UIImage *image))block;
Github代码 FGAsyncLoadImgUtil
以上就是项目中使用的一个小优化方案,如果同学们其他idea,希望不吝赐教。