1. os_signpost是什么
signpost这个单词是路标、指示牌的意思,比如距离北京还有158km。顾名思义,os_signpost就是在代码里面插入一些标记,作用相当于路标。官方文档是这样描述的:
The os_signpost APIs let clients add lightweight instrumentation to
code for collection and visualization by performance analysis tooling.
用人话说就是:os_signpost是一个轻量级的可视化的性能分析工具。
2. os_signpost的提出背景
代码层面的性能分析,最直观的方式就是标识出一段代码的开始和结尾,然后计算下耗时。就像下面这样:
CFTimeInterval begin = CACurrentMediaTime();
// do something
CFTimeInterval end = CACurrentMediaTime();
NSLog(@"cost = %@",(end - begin));
然而如果代码逻辑复杂、有先后关系、存在多个线程等,单靠某小段代码的标记,就不是那么直观了。
于是,在2018年9月,苹果推出了os_signpost,它可以配合Instruments,显示可视化的效果,WWDC视频Measuring Performance Using Logging有介绍。因为是18年才推出,所以os_signpost只支持iOS12及以上系统,Xcode10及更高版本。
3. os_signpost的用法
这里以OC代码为例。
(1) 先导入头文件,为了使用方便,再定义两个宏:
#include <os/signpost.h>
#define INNER_BEGIN_LOG(subsystem, category, name) \
os_log_t m_log_##name = os_log_create((#subsystem), (#category));\
os_signpost_id_t m_spid_##name = os_signpost_id_generate(m_log_##name);\
os_signpost_interval_begin(m_log_##name, m_spid_##name, (#name));
#define INNER_END_LOG(name) \
os_signpost_interval_end(m_log_##name, m_spid_##name, (#name));
这两个宏是成对使用的,这段代码可以放公共的头文件里,方便不同的地方用。
(2)给代码插入路标:
页面初始化的时候插入名字为init
的标记,
viewDidAppear的时候插入名字为viewDidAppear
的标记,
接口请求回调时插入名字为requestDidCompleted
的标记,
数据处理完插入名字为requestProcessComplete
的标记。
- (id)init {
self = [super init];
if (self) {
if (@available(iOS 12.0, *)) {
INNER_BEGIN_LOG(fourPage, init, init);
INNER_END_LOG(init);
}
// do something
}
return self;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// do something
if (@available(iOS 12.0, *)) {
INNER_BEGIN_LOG(fourPage, init, viewDidAppear);
INNER_END_LOG(viewDidAppear);
}
}
- (void)requestDidCompleted {
if (@available(iOS 12.0, *)) {
INNER_BEGIN_LOG(fourPage, init, requestDidCompleted);
INNER_END_LOG(requestDidCompleted);
}
// do something
if (@available(iOS 12.0, *)) {
INNER_BEGIN_LOG(fourPage, init, requestProcessComplete);
INNER_END_LOG(requestProcessComplete);
}
}
(3)工程设置
Debug Information Format
设置为DWARF with dSYM File
,如下图所示:
这一步是为了后面在Instruments里面能直接看到代码。
现在就可以插上手机跑起来了。
(4)配合Instruments
打开Instruments的Time Profiler,默认会有一行Points of Interest,这里用不到,可以把它删了,选中再按删除键即可。然后添加os_signpost,如下图所示:把这个模板保存起来,点上面的File->Save As Template...,输入名字signpost。
选择刚刚跑的app,点左上角的红色按钮开始录制,这时候界面显示Recording,操作完了再次点左上角按钮结束录制,然后进入下面这样的界面:
刚录制完可能看不到刚刚插入的那些标记,只要在上图中红线位置往下拖,把界面展开就看到了,如下图所示。左边红框就是添加的4个标记,右边红框里的竖线是相应的时间。把鼠标放在右边红框位置,滚动滚轮,可以缩放时间轴,笔记本用户可以用触摸板缩放。
时间轴放大后可以清晰地看到,从init到viewDidApper整个过程耗时840ms,其中接口请求耗时405ms,接口解析耗时35ms,页面渲染耗时400ms,还可以看到这段时间主线程压力很大。
以我的代码为例,可以计算出onlineChatBtn加载图片用了48ms,而它的父视图初始化用了305ms,这些都是在主线程执行的。
4. 小结
通过以上这些操作,我们能够看到两个标记之间具体执行了哪些代码,以及哪些代码比较耗时,进而可以有针对性地进行优化。