一些有深度的博客我会同步到自己的Github上
这个源码解析系列的文章
前言
最近心血来潮,想研究下FaceBook的AsnycDispalyKit的源代码,学习一些界面优化的技术以及编码风格。这篇文章,会详细的记录下我认为对新手有用的部分。后面有空的时候,继续研究其他几个iOS开发很流行的库-AFNetworking,SDWebImage,MBProgressHud,Mantle等`。AsnycDisplayKit是一个非常庞大的库,所以我尽量捞干的讲。
关于AsyncDisplayKit
如果只是想优化界面,那么可以用AsyncDisplayKit来重写哪些性能要求比较高的部分
对了,阅读YYKit的作者ibireme的《iOS 保持界面流畅的技巧》一文对我的启发很大,建议读者可以看看他的文章,真的写得很好,是国内少有的iOS开发大神。
界面顿卡的原因
iOS的屏幕是60fps,也就是说,每一帧的间隔是1/60s,大概16.7ms。
每一帧显示需要三步
- CPU计算好视图(UIView)的大小,位置,对图片进行解码,绘制好纹理交给GPU
- GPU根据纹理,顶点进行空间变换,渲染后放到帧缓冲区
- 每当帧信号到达的时候,从帧缓冲区取一帧,显示到屏幕上
也就是说,整个CPU+GPU处理的时间是16.7ms,如果超过这个时间,那么当前绘制的一帧就没办法放到帧缓冲区,帧信号到达的时候,取的还是上一帧的数据。也就是造成了界面没有变化,显示顿卡。也就是说,为了解决顿卡,一般要从CPU和GPU两个角度来考虑
CPU限制
- 对象的创建,释放,属性调整。这里尤其要提一下属性调整,CALayer的属性调整的时候是会创建隐式动画的,是比较损耗性能的。
- 视图和文本的布局计算,AutoLayout的布局计算都是在主线程上的,所以占用CPU时间也很多 。
- 文本渲染,诸如UILabel和UITextview都是在主线程渲染的
- 图片的解码,这里要提到的是,UIImage只有在交给GPU之前的一瞬间,CPU才会对其解码。
GPU限制
- 视图的混合,比如一个界面十几层的视图叠加到一起,GPU不得不计算每个像素点药显示的像素
- 视图的Mask,比如圆角什么的,会触发离屏渲染,占用GPU时间。
- 半透明,GPU不得不进行数学计算,如果是不透明的,CPU只需要取上层的就可以了
- 浮点数像素
AsnycDisplayKit通过很多技巧来解决这些问题,后文我会一点点分析如何实现的。
AsyncDisplayKit是啥
这是Facebook推出的一个框架,用在Paper的App中。用来保证复杂的界面交互的时候,也不会掉帧。
通过名字就可以看出来,AsyncDisplay就是异步加载控件。了解UIKit的同学都知道,UIKit的中的UIView和CALayer的布局和渲染都是在主线程上进行的,当界面复杂的时候,也就会占用大量时间导致掉帧。这个框架是建立在UIKit之上的,对UIView进行了进一步的封装-Node。Node支持异步的绘制UIView。Asnyc有一个原则
- 能放到后台执行的代码就尽量放到后台,不能放到后台执行的代码就尽量优化(比如用Runloop对任务进行拆分)
ASDealloc2MainObject
这个类中,AsnycDisplayKit重新定义了Release和Reatin方法来让一个类支持自己引用计数,可以强制的让对象在主线程dealloc,不过这个文件是MRC的,也就是要在build setting中添加-fno-objc-arc
。
那么,为什么要强制的在主线程dealloc呢?因为UIKit的对象不是线程安全的,只能在主线程上进行dealloc
Tips:
1.可以在文件中,添加如下代码
#if __has_feature(objc_arc)
#error This file must be compiled without ARC. Use -fno-objc-arc.
#endif
来让编译器检查本文件只能在MRC条件下编译。
关于如何重写Release和Reatian,可以在这个文件里找到_AS-objc-internal.h。
2.由于Define只是在编译期进行简单替换,可以通过#defeine的方式为类条件添加代码
大量的断言和宏定义
通过阅读源代码可以发现,代码中使用了大量的宏和断言
- (void)dealloc
{
ASDisplayNodeAssertMainThread();
//Other codes
}
其中ASDisplayNodeAssertMainThread()
为宏定义,
#define ASDisplayNodeAssertWithSignal(condition, description, ...) NSAssert(condition, description, ##__VA_ARGS__)
#define ASDisplayNodeAssertMainThread() ASDisplayNodeAssertWithSignal([NSThread isMainThread], nil, @"This method must be called on the main thread")
对于断言和宏定义使用很少的同学,可以看看这个头文件,会对你很有帮助。
Tips:
合理的使用断言NSAssert,能够让你的代码在更早的地方出现问题,方便发现问题进行调试。在XCode 7中,NSAssert默认只会在Debug模式下起作用,在release模式下不会起作用,
Objective C++
在AsyncDisplay中,可以看到很多.mm后缀的文件,例如
Objective C++和Objective C类似,它的文件组成由一个.h和一个.mm组成&#x