一、视图加载
优质文章推荐
- iOS 界面渲染流程分析
我们可以看到,在Application这一层中主要是CPU在操作,而到了Render Server这一层,CoreAnimation会将具体操作转换成发送给GPU的draw calls(以前是call OpenGL ES,现在慢慢转到了Metal),显然CPU和GPU双方同处于一个流水线中,协作完成整个渲染工作。
整个渲染分为三个步骤
- 1、CPU 阶段
- 布局(Frame)
- 显示(Core Graphics)
- 准备(QuartzCore/Core Animation)
- 通过 IPC 提交(打包好的图层树以及动画属性)
- 2、OpenGL ES(Metal) 阶段
- 生成(Generate)
- 绑定(Bind)
- 缓存数据(Buffer Data)
- 启用(Enable)
- 设置指针(Set Pointers)
- 绘图(Draw)
- 清除(Delete)
- 3、GPU 阶段
- 接收提交的纹理(Texture)和顶点描述(三角形)
- 应用变换(transform)
- 合并渲染(离屏渲染等)
二、 帧率与丢帧
三、 卡顿原理
四、 卡顿检测&优化
- 质量监控-卡顿检测
1. 检测方法
- 主线程相关
- fps :我们可以根据CADisplayLink刷新的
时间间隔
,以及刷新的频率来
判断是否有卡顿
// fps 监控
- (void)startFpsMonitoring {
CADisplayLink * fpsDisplay = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayFps:)];
[fpsDisplay addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)displayFps: (CADisplayLink *)fpsDisplay {
static NSInteger count; // 没刷新一次 计数加一
static double lastUpadateTime; // 最后的统计时间
count++;
CFAbsoluteTime threshold = CFAbsoluteTimeGetCurrent() - lastUpadateTime; // 一个周期用了多少时间
if (threshold >= 1.0) { //
NSLog(@"一个周期内刷新了%f下,每%f毫秒刷新一下",(count / threshold),fpsDisplay.duration); // 一个周期内 刷新了多少下
lastUpadateTime = CFAbsoluteTimeGetCurrent();
count = 0;
}
}
2020-07-22 10:40:04.997406+0800 PHTemplate[18396:2917339] [Storyboard] Unknown class ViewController in Interface Builder file.
2020-07-22 10:40:05.034140+0800 PHTemplate[18396:2917339] 375.000000
2020-07-22 10:40:05.042258+0800 PHTemplate[18396:2917339] 一个周期内刷新了0.000000下,每0.016667毫秒刷新一下
2020-07-22 10:40:05.053648+0800 PHTemplate[18396:2917339] Unbalanced calls to begin/end appearance transitions for .
2020-07-22 10:40:06.053587+0800 PHTemplate[18396:2917339] 一个周期内刷新了60.328246下,每0.016667毫秒刷新一下
2020-07-22 10:40:07.054198+0800 PHTemplate[18396:2917339] 一个周期内刷新了59.977028下,每0.016667毫秒刷新一下
2020-07-22 10:40:08.070924+0800 PHTemplate[18396:2917339] 一个周期内刷新了60.011309下,每0.016667毫秒刷新一下
- ping : 是一种常用的网络测试工具,用来测试数据包是否能到达ip地址。在卡顿发生的时候,主线程会出现短时间内无响应这一表现,基于ping的思路
从子线程尝试通信主线程
来获取主线程的卡顿延时:
// 子线程与主线程通讯
- (void)ping {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (![NSThread currentThread].isCancelled) {
dispatch_sync(dispatch_get_main_queue(), ^{
static CFAbsoluteTime pingTime;
NSLog(@"%f",CFAbsoluteTimeGetCurrent()-pingTime);
pingTime = CFAbsoluteTimeGetCurrent();
});
};
});
}
- runloop
- (void)startRunLoopMonitoring {
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
if (CFAbsoluteTimeGetCurrent() - _lastActivityTime >= _threshold) {
......
_lastActivityTime = CFAbsoluteTimeGetCurrent();
}
});
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}
- 非主线程
- stack backtrace
代码质量不够好的方法可能会在一段时间内持续占用CPU的资源,换句话说在一段时间内,调用栈总是停留在执行某个地址指令的状态。由于函数调用会发生入栈行为,如果比对两次调用栈的符号信息,前者是后者的符号子集时,可以认为出现了卡顿恶鬼:
@interface StackBacktrace : NSThread
......
@end
@implementation StackBacktrace
- (void)main {
[self backtraceStack];
}
- (void)backtraceStack {
while (!self.cancelled) {
@autoreleasepool {
NSSet *curSymbols = [NSSet setWithArray: [StackBacktrace backtraceMainThread]];
if ([_saveSymbols isSubsetOfSet: curSymbols]) {
......
}
_saveSymbols = curSymbols;
[NSThread sleepForTimeInterval: _interval];
}
}
}
@end
- msgSend observe
OC方法的调用最终转换成msgSend的调用执行,通过在函数前后插入自定义的函数调用,维护一个函数栈结构可以获取每一个OC方法的调用耗时,以此进行性能分析与优化:
#define save() \
__asm volatile ( \
"stp x8, x9, [sp, #-16]!\n" \
"stp x6, x7, [sp, #-16]!\n" \
"stp x4, x5, [sp, #-16]!\n" \
"stp x2, x3, [sp, #-16]!\n" \
"stp x0, x1, [sp, #-16]!\n");
#define resume() \
__asm volatile ( \
"ldp x0, x1, [sp], #16\n" \
"ldp x2, x3, [sp], #16\n" \
"ldp x4, x5, [sp], #16\n" \
"ldp x6, x7, [sp], #16\n" \
"ldp x8, x9, [sp], #16\n" );
#define call(b, value) \
__asm volatile ("stp x8, x9, [sp, #-16]!\n"); \
__asm volatile ("mov x12, %0\n" :: "r"(value)); \
__asm volatile ("ldp x8, x9, [sp], #16\n"); \
__asm volatile (#b " x12\n");
__attribute__((__naked__)) static void hook_Objc_msgSend() {
save()
__asm volatile ("mov x2, lr\n");
__asm volatile ("mov x3, x4\n");
call(blr, &push_msgSend)
resume()
call(blr, orig_objc_msgSend)
save()
call(blr, &pop_msgSend)
__asm volatile ("mov lr, x0\n");
resume()
__asm volatile ("ret\n");
}
2. 卡顿的解决方法
- iOS开发-常用解决页面卡顿技巧
五、ASDK分析(AsyncDisplayKit):已经换库Texture
- ASDK源码剖析
六、离屏渲染
离屏渲染的主要原因就是,多了一些渲染完成后又要修改的情况
关于iOS离屏渲染的深入研究