一、图片到屏幕view过程
1、数据处理过程
图片数据
- 1)将原始图片数据(png/jpg等)加载到内存中
- 2)CPU解压图片数据
- 3)渲染图片数据渲染到屏幕上
2、硬件协同处理细节
硬件处理- 1)CPU:计算视图frame,图片解码,需要绘制纹理图片通过数据总线交给GPU
- 2)GPU: 纹理混合,顶点变换与计算,像素点的填充计算,渲染到帧缓冲区
- 3)时钟信号:垂直同步信号V-Sync / 水平同步信号H-Sync
- 4)iOS设备双缓冲机制:显示系统通常会引入两个帧缓冲区,双缓冲机制
二、图片加载到渲染的工作流程
1、从文件加载图片数据 可以使用类方法+imageNamed:
,此时我们得到图片原始数据
2、将UIImage
赋值给UIImageView
3、CATransaction
捕获到UIImageView
图层树的变化
4、主线程runloop提交 CATransaction,开始进行解码和图像渲染,这里会涉及到
- 如果图像数据为未解码的PNG/JPG,解码为位图数据
- GPU处理数据需要字节对齐,Core Animation会拷贝一份数据,进行字节对齐
- GPU处理位图数据,进行渲染
5、渲染
- GPU获取获取图片的坐标
- 将坐标交给顶点着色器(顶点计算)
- 将图片光栅化(获取图片对应屏幕上的像素点)
- 片元着色器计算(计算每个像素点的最终显示的颜色值)
- 从帧缓存区中渲染到屏幕上
注意:
1、图片只有在确认需要显示时,CPU才对其进行解压;+imageName:
or+imageNamed:inBundle:compatibleWithTraitCollection:
加载的图片,系统会对其自动缓存,以便下次使用,但如果用+imageWithContentsOfFile:
、-initWithContentsOfFile:
系统会每次都从disk加载,对应的都需要解压;imageWithData
直接读取原始数据,如果非GPU能直接处理的数据(位图或矢量图)是需要解压的;
2、解码是一个比较消耗CPU的操作,且默认在主线程,当界面上一次性确定显示多张图片时表现得尤为突出,此时一般的做法可以将解码操作放到异步线程去完成,解码后再用来展示
三、探究YYImage
1、YYImage 类结构分析
- 图像层:YYImage, YYFrameImage, YYSpriteSheetImage
- 视图层:YYAnimatedImageView
- 编/解码层:YYImageCoder
2、分析YYAnimatedImageView
- 初始化
- (instancetype)init {
self = [super init];
_runloopMode = NSRunLoopCommonModes;
_autoPlayAnimatedImage = YES;
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
_runloopMode = NSRunLoopCommonModes;
_autoPlayAnimatedImage = YES;
return self;
}
- (instancetype)initWithImage:(UIImage *)image {
self = [super init];
_runloopMode = NSRunLoopCommonModes;
_autoPlayAnimatedImage = YES;
self.frame = (CGRect) {CGPointZero, image.size };
self.image = image;
return self;
}
初始化中指定了runloopMode
为NSRunLoopCommonModes
- 动画的
link
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode];
_runloopMode 指定到了 CADisplayLink
的link
中,不断地替换_curFrame
用来跟新layer
里面显示的图片frame
,将link
的指定成NSRunLoopCommonModes
保证了在拖动滚动视图时动画还能继续。
- 图片解压
_YYAnimatedImageViewFetchOperation
//创建_requestQueue 设置最大并发量1
_requestQueue = [[NSOperationQueue alloc] init];
_requestQueue.maxConcurrentOperationCount = 1;
//解码操作加入_requestQueue
_YYAnimatedImageViewFetchOperation *operation = [_YYAnimatedImageViewFetchOperation new];
operation.view = self;
operation.nextIndex = nextIndex;
operation.curImage = image;
[_requestQueue addOperation:operation];
@implementation _YYAnimatedImageViewFetchOperation
- (void)main {
...
for (int i = 0; i < max; i++, idx++) {
@autoreleasepool {
...
if (miss) {
//解码图片,并且标记
UIImage *img = [_curImage animatedImageFrameAtIndex:idx];
img = img.yy_imageByDecoded;
if ([self isCancelled]) break;
LOCK_VIEW(view->_buffer[@(idx)] = img ? img : [NSNull null]);
view = nil;
}
}
}
}
@end
可以看到,_YYAnimatedImageViewFetchOperation
解码image图片的操作是通过
NSOperationQueue
的_requestQueue
来异步来处理的
- 图片数据buffer缓存
NSMutableDictionary *_buffer; ///< frame buffer
这里cache了显示所需要解压好的图片buffer数据,以便重复使用; 当然这里还有缓存限制和memory告警时清理缓存的机制,我这里就不一一赘述了
参考链接:
https://developer.apple.com/documentation/uikit/uiimage
https://www.jianshu.com/p/72dd074728d8
https://developer.apple.com/documentation/uikit/uiimage
网友评论