一、YYLabel实现的总体思路
其实YYLabel主要就是为了实现异步绘图,提高性能,为此做了好多东西。
1、使用自定义的Layer,YYAsyncLayer
2、在YYAsyncLayer中实现异步绘图,因为系统默认的绘图都是在主线程执行了的,这里要实现异步绘图,那么就得摈弃掉系统的绘图方法,自己去实现。所以这里得花费很多功夫。所以说,提高性能是要付出很多的。
二、下面开始进入代码分析:
(1)使用自定义的Layer,YYAsyncLayer
在YYLabel中重写了下面的方法:
+ (Class)layerClass {
return [YYAsyncLayer class];
}
就是修改了View的主Layer层,把YYAsyncLayer的实例作为主Layer层
(2)View与Layer的关系
View其实就是Layer的代理,Layer是View的底层实现,View是Layer的管理类。绘图和坐标等操作都是在Layer中实现的,View只是访问Layer中的相关方法。
(3)系统View的绘图过程:
1、调用setNeedsDisplay
2、调用Layer中的display方法
3、在display中会调用Layer的drawInContext方法
4、在drawInContext中会调用代理方法drawLayer:(CALayer *)layer
inContext:(CGContextRef)ctx,这就进入到View了
5、最后View在代理实现中调用了drawRect方法,这方法就是我们经常用到了那个。
我写了demo验证了上面的过程,结果如下:
(4) YYLabe的绘图过程
在YYAsyncLayer中,我们找到display方法:
- (void)display {
super.contents = super.contents;
[self _displayAsync:_displaysAsynchronously];
}
重写了display方法,但没有调用super方法,就是说上面的流程已经被打断了,那么view的系统绘图方法都不会被调用。
通过代理方法newAsyncDisplayTask得到了YYAsyncLayerDisplayTask类实例,再通过调用类实例的block方法直接返回到View中进行操作。作者打断了之前的绘图流程,但这里是通过block进入到UIView中进行绘图操作,这样就可以完全控制绘图过程。
在_displayAsync方法中:
可见,这里是在线程中操作的,先创建了上下文context,然后再调用代理方法:task.display(context, size,isCancelled),所以UIView在代理实现中的绘图操作都是在线程中执行的,这里就实现了异步绘图。
(5) newAsyncDisplayTask的代理实现
上面是得到一些绘图需要的参数,其中最主要的是YYTextLayout对象,里面包含了很多绘图需要的信息。
然后是block display方法:
上面提到这个block实在非主线程执行的,这里执行了真正的绘图操作,绘图操作方法:
[drawLayoutdrawInContext:context size:size point:point view:nil layer:nil debug:debug cancel:isCancelled];中。
具体的绘图还是挺复杂了,看起来有点吃力,以后有机会再研究下。好了,整个YYLabel的实现流程已经分析完成,是不是觉得源码作者很牛逼。作者为了实现异步绘图,花了好多的功夫。
总结:
所以整体的流程是:首先得理解CALayer与UIView的关系,UIView就是CALayer的代理,CALayer实现真正的绘制工作,绘制好之后,再通过代理传递到UIView的drawRect方法,给UIView进一步绘制的工作,不过UIView也基本不需要在drawRect做什么东西。
YYkit利用这个原理。自定义了自己的YYLayer和YYLabel,通过在YYLabel上的layerClass关联YYLayer。因为不能使用CALayer的绘制代码(UIKit那套是不能在子线程执行),所有必须打断掉以前CALayer的执行顺序(不让它使用之前的绘制)。YYKit在YYLayer上重写了display方法,打断了以前的顺序,让它不去执行之前的绘制。接着在display方法里,通过代理从YYLabel得到一个task,YYLabel实现task.display()。YYLayer创建一个上下文画板,然后通过在子线程调用task.display(context)传递画板-context给YYLabel,YYLabel在画板上用coreGraphic绘制正在的内容。绘制好后,在YYLayer上通过主线程返回给layer的self.contents,画面就展示出来了。