文章目录
一、os_signpost 是什么
os_signpost 是 Apple 提供的一套用于标记代码性能的工具,能够帮助开发者精准测量和分析代码执行的耗时及行为。它是 os_log 系统的一部分,可以与 Instruments 的 Points of Interest 和 Time Profiler 等模板结合使用。
- os_log_t:日志句柄,用于标识一个子系统和类别。
- os_signpost_id_t:标记 ID,用于区分不同的事件或时间区间。
- os_signpost 类型:
- os_signpost_event_emit:标记一个瞬时事件。
- os_signpost_interval_begin / os_signpost_interval_end:标记时间区间的开始和结束。
二、使用方法
1. 必要的头文件
使用 os_signpost 需要导入头文件:
#import <os/log.h>
#import <os/signpost.h>
2. 创建 os_log_t 日志句柄
os_log_t 用于标识日志的子系统和分类,通常初始化一次并在整个类中复用:
os_log_t log = os_log_create("com.yourapp.subsystem", "Category");
第一个参数:subsystem - 一般是应用的标识符(如 com.yourapp)。
第二个参数:category - 分类名称(如 Performance、PointsOfInterest)。
3. os_signpost 的核心用法
标记时间区间(Interval)
用于测量一段代码的执行时间。
os_log_t log = os_log_create("com.yourapp.performance", "ImageDownload");
os_signpost_id_t signpostID = os_signpost_id_generate(log);
// 开始标记
os_signpost_interval_begin(log, signpostID, "Image Download");
// 模拟耗时操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
// 结束标记
os_signpost_interval_end(log, signpostID, "Image Download");
});
标记瞬时事件(Event)
用于记录一个单一的事件,例如方法调用、状态变化。
os_log_t log = os_log_create("com.yourapp.performance", "PointsOfInterest");
os_signpost_id_t signpostID = os_signpost_id_generate(log);
os_signpost_event_emit(log, signpostID, "Single Event Occurred");
为时间区间添加元数据
可以为区间或事件标记附加格式化的元数据,以便在 Instruments 中更好地分析。
os_log_t log = os_log_create("com.yourapp.performance", "PointsOfInterest");
os_signpost_id_t signpostID = os_signpost_id_generate(log);
int imageIndex = 5;
// 带元数据开始标记
os_signpost_interval_begin(log, signpostID, "Image Download Index: %d", imageIndex);
// 模拟下载完成
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
os_signpost_interval_end(log, signpostID, "Image Download Finished Index: %d", imageIndex);
});
三、优点
- 轻量级:对性能影响极小,可以安全地用于生产环境。
- 精准追踪:支持瞬时事件和时间区间的跟踪。
- 可视化分析:与 Instruments 集成,提供直观的时间线和数据。
四、使用的全流程
首先准备一段代码,并设置好要分析代码耗时的区间:
#import "ViewController.h"
#import <os/log.h>
#import <os/signpost.h>
@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) NSMutableArray<UIImage *> *images;
@property NSMutableArray<NSString *> *imageUrlString;
@property (nonatomic, strong) os_log_t log;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.log = os_log_create("com.wml.getPic", "PointsOfInterest");
os_signpost_id_t initViewID = os_signpost_id_generate(self.log);
os_signpost_interval_begin(self.log, initViewID, "ViewDidLoad - begin");
// 初始化图片数组
self.images = [NSMutableArray array];
// 创建并配置 UICollectionView
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.itemSize = CGSizeMake(100, 100);
layout.minimumInteritemSpacing = 10;
layout.minimumLineSpacing = 10;
layout.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10); // 设置边距
self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
self.collectionView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.collectionView];
os_signpost_interval_end(self.log, initViewID, "ViewDidLoad - begin");
// 下载图片
[self downloadImages];
NSLog(@"下载图片ing...");
}
- (void)downloadImages {
os_signpost_id_t getUrlId = os_signpost_id_generate(self.log);
os_signpost_interval_begin(self.log, getUrlId, "Init Image Url - start");
// 假设我们有一个包含 100 个图片 URL 的数组
NSMutableArray<NSURL *> *imageURLs = [NSMutableArray array];
[self initURLs];
for (int i = 0; i < self.imageUrlString.count; i++) {
NSString *urlString = self.imageUrlString[i];
[imageURLs addObject:[NSURL URLWithString:urlString]];
}
os_signpost_interval_end(self.log, getUrlId, "Init Image Url - start");
for (NSURL *url in imageURLs) {
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
os_signpost_id_t downloadImageID = os_signpost_id_generate(self.log);
os_signpost_interval_begin(self.log, downloadImageID, "Download Image - start");
if (data && !error) {
UIImage *image = [UIImage imageWithData:data];
if (image) {
@synchronized (self.images) {
[self.images addObject:image];
}
dispatch_async(dispatch_get_main_queue(), ^{
os_signpost_interval_end(self.log, downloadImageID, "Download Image - start");
[self.collectionView reloadData];
});
}
}
}];
[task resume];
}
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.images.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
// 移除旧的UIImageView
for (UIView *subview in cell.contentView.subviews) {
[subview removeFromSuperview];
}
UIImageView *imageView = [[UIImageView alloc] initWithFrame:cell.contentView.bounds];
imageView.image = self.images[indexPath.item];
imageView.contentMode = UIViewContentModeScaleAspectFit;
[cell.contentView addSubview:imageView];
return cell;
}
- (void)initURLs {
self.imageUrlString = @[
@"https://s2.loli.net/2024/07/03/xOo3V2btpDKIMJE.jpg",
@"https://s2.loli.net/2024/07/03/Cj2P4QA7OKdxDwu.jpg",
@"https://s2.loli.net/2024/07/03/ODqIC73HGhKoamF.jpg",
@"https://s2.loli.net/2024/07/03/MR7NweS54E6uWln.jpg",
@"https://s2.loli.net/2024/07/03/n7TrzS21FAuta5X.jpg",
@"https://s2.loli.net/2024/07/03/uI4RdqWAcjPQ5HJ.jpg",
@"https://s2.loli.net/2024/07/03/Wr6wzbGoc1q7Zk5.jpg",
@"https://s2.loli.net/2024/07/03/kYAfKo2zc3hGvn6.jpg",
@"https://s2.loli.net/2024/07/03/j5qmWdMZ9wgP8Rx.jpg",];
}
点击Product中的Profile:
然后选择:
点击运行:
最后我们可以看到耗时的统计数据:
五、注意事项
根据提示,我们需要将 os_log_create
的第二个参数设置为 PointsOfInterest
才能捕获到耗时!
os_log_t log = os_log_create("com.yourapp.subsystem", "PointsOfInterest");
这两个方法的第三个参数要一致,才能捕获到区间代码的耗时!
os_signpost_interval_begin(log, interval_id, name, ...)
os_signpost_animation_interval_begin(log, interval_id, name, ...)