iOS性能优化01 -- 卡顿优化

2021-12-30  本文已影响0人  YanZi_33
CPU与GPU
屏幕成像
image.png
图像的相关概念
画面撕裂
image.png
画面跳帧
画面闪烁
解决方案
画面(屏幕)卡顿
image.png image.png
iOS中卡顿的监测
卡顿监测的第一种方案:利用CADisplayLink计算GPU的帧率是否达到60FPS
#import "ViewController.h"

@interface ViewController ()

@property(nonatomic,strong)UILabel *FPSLabel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //主线程 注意有内存泄漏
    [[CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkAction:)] addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    
    [self.view addSubview:self.FPSLabel];
}

- (void)displayLinkAction:(CADisplayLink *)link {
    //静态变量记录上次执行回调方法的时间戳
    static NSTimeInterval lastTime = 0;
    //静态变量记录回调方法执行的次数
    static NSInteger frameCount = 0;
    if (lastTime == 0) {
        lastTime = link.timestamp;
        return;
    }
    frameCount ++;
    //当CADisplayLink的时间间隔累积到1秒时 计算回调方法执行的次数
    //计算得到 每秒钟 回调执行的次数 作为GPU的渲染帧率 看是否能达到60帧/s 由此可判定主线程是否卡顿;
    NSTimeInterval paseTime = link.timestamp - lastTime;
    if (paseTime >= 1) {
        NSInteger fps = frameCount / paseTime;
        lastTime = link.timestamp;
        frameCount = 0;
        NSLog(@"fps = %ld",fps);
        self.FPSLabel.text = [NSString stringWithFormat:@"%ldFPS",fps];
    }
}

- (UILabel *)FPSLabel{
    if (!_FPSLabel) {
        _FPSLabel = [[UILabel alloc]init];
        _FPSLabel.font = [UIFont systemFontOfSize:16];
        _FPSLabel.textColor = [UIColor whiteColor];
        _FPSLabel.backgroundColor = [UIColor grayColor];
        _FPSLabel.textAlignment = NSTextAlignmentCenter;
        _FPSLabel.frame = CGRectMake([UIScreen mainScreen].bounds.size.width - 100 - 30, [UIScreen mainScreen].bounds.size.height - 100, 100, 30);
    }
    return _FPSLabel;
}
@end
卡顿监测的第二种方案:RunLoop监听应用程序卡顿
#import <Foundation/Foundation.h>
#define SHAREDMONITOR [LXDAppFluecyMonitor sharedMonitor]
/*!
 *  @brief  监听UI线程卡顿
 */
@interface LXDAppFluecyMonitor : NSObject
+ (instancetype)sharedMonitor;
- (void)startMonitoring;
- (void)stopMonitoring;
@end
#import "LXDAppFluecyMonitor.h"

#define LXD_DEPRECATED_POLLUTE_MAIN_QUEUE

@interface LXDAppFluecyMonitor ()
@property (nonatomic, assign) int timeOut;
@property (nonatomic, assign) BOOL isMonitoring;
@property (nonatomic, assign) CFRunLoopObserverRef observer;
@property (nonatomic, assign) CFRunLoopActivity currentActivity;
@property (nonatomic, strong) dispatch_semaphore_t semphore;
@property (nonatomic, strong) dispatch_semaphore_t eventSemphore;
@end

#define LXD_SEMPHORE_SUCCESS 0
static NSTimeInterval lxd_restore_interval = 5;
static NSTimeInterval lxd_time_out_interval = 1;
static int64_t lxd_wait_interval = 200 * NSEC_PER_MSEC;

/*!
 *  @brief  监听runloop状态在after waiting和before sources之间
 */
static inline dispatch_queue_t lxd_fluecy_monitor_queue() {
    static dispatch_queue_t lxd_fluecy_monitor_queue;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        lxd_fluecy_monitor_queue = dispatch_queue_create("com.sindrilin.lxd_monitor_queue", NULL);
    });
    return lxd_fluecy_monitor_queue;
}

#define LOG_RUNLOOP_ACTIVITY 0
static void lxdRunLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void * info) {
    SHAREDMONITOR.currentActivity = activity;
    dispatch_semaphore_signal(SHAREDMONITOR.semphore);
#if LOG_RUNLOOP_ACTIVITY
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"runloop entry");
            break;
        case kCFRunLoopExit:
            NSLog(@"runloop exit");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"runloop after waiting");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"runloop before timers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"runloop before sources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"runloop before waiting");
            break;
        default:
            break;
    }
#endif
};

@implementation LXDAppFluecyMonitor

#pragma mark - Singleton override
+ (instancetype)sharedMonitor {
    static LXDAppFluecyMonitor * sharedMonitor;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        sharedMonitor = [[super allocWithZone: NSDefaultMallocZone()] init];
        [sharedMonitor commonInit];
    });
    return sharedMonitor;
}

+ (instancetype)allocWithZone: (struct _NSZone *)zone {
    return [self sharedMonitor];
}

- (void)dealloc {
    [self stopMonitoring];
}

- (void)commonInit {
    self.semphore = dispatch_semaphore_create(0);
}

#pragma mark - Public
- (void)startMonitoring {
    if (_isMonitoring) { return; }
    _isMonitoring = YES;
    CFRunLoopObserverContext context = {
        0,
        (__bridge void *)self,
        NULL,
        NULL
    };
    //创建监听者
    _observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &lxdRunLoopObserverCallback, &context);
    //监听主RunLoop
    CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
    
    //创建子线程
    dispatch_async(lxd_fluecy_monitor_queue(), ^{
        NSLog(@"%@",[NSThread currentThread]);
        while (SHAREDMONITOR.isMonitoring) {
            //成功为0,表示在指定时间内接收到主线程发出的信号
            //不成功非0,表示在指定时间内没有接收到主线程发出的信号,主线程可能在执行耗时任务,有可能造成应用程序的卡顿
            long waitTime = dispatch_semaphore_wait(self.semphore, dispatch_time(DISPATCH_TIME_NOW, lxd_wait_interval));
            if (waitTime != LXD_SEMPHORE_SUCCESS) {
                if (!SHAREDMONITOR.observer) {
                    SHAREDMONITOR.timeOut = 0;
                    [SHAREDMONITOR stopMonitoring];
                    continue;
                }
                //kCFRunLoopBeforeSources 主RunLoop开始处理事件
                //kCFRunLoopAfterWaiting  主RunLoop结束休眠
                //状态判断 即在kCFRunLoopBeforeSources或kCFRunLoopAfterWaiting这两个状态区间内出现耗时
                if (SHAREDMONITOR.currentActivity == kCFRunLoopBeforeSources || SHAREDMONITOR.currentActivity == kCFRunLoopAfterWaiting) {
                    //出现5次耗时 则上传主线程的函数调用栈
                    if (++SHAREDMONITOR.timeOut < 5) {
                        continue;
                    }
                    [LXDBacktraceLogger lxd_logMain];
                    [NSThread sleepForTimeInterval: lxd_restore_interval];
                }
            }
            SHAREDMONITOR.timeOut = 0;
        }
    });
}

- (void)stopMonitoring {
    if (!_isMonitoring) { return; }
    _isMonitoring = NO;
    
    CFRunLoopRemoveObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
    CFRelease(_observer);
    _observer = nil;
}
@end
#import "ViewController.h"
#import "LXDAppFluecyMonitor.h"

@interface ViewController ()<UITableViewDelegate, UITableViewDataSource>

@property (weak, nonatomic) IBOutlet UITableView *tableView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [[LXDAppFluecyMonitor sharedMonitor] startMonitoring];
    [self.tableView registerClass: [UITableViewCell class] forCellReuseIdentifier: @"cell"];
}

- (void)viewDidAppear: (BOOL)animated {
    [super viewDidAppear: animated];
}

- (NSInteger)tableView: (UITableView *)tableView numberOfRowsInSection: (NSInteger)section {
    return 1000;
}

- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: @"cell"];
    cell.textLabel.text = [NSString stringWithFormat: @"%lu", indexPath.row];
    if (indexPath.row > 0 && indexPath.row % 30 == 0) {
       //等价于sleep(2)
        usleep(2 * 1000 * 1000);
    }
    return cell;
}

- (void)tableView: (UITableView *)tableView didSelectRowAtIndexPath: (NSIndexPath *)indexPath {
    usleep(2 * 1000 * 1000);
}
@end
dispatch_async(lxd_event_monitor_queue(), ^{
    NSLog(@"%@",[NSThread currentThread]);
    while (SHAREDMONITOR.isMonitoring) {
        //主线程的RunLoop 即将进入休眠
        if (SHAREDMONITOR.currentActivity == kCFRunLoopBeforeWaiting) {
            //默认超时
            __block BOOL timeOut = YES;
            NSLog(@"0");
            
            dispatch_async(dispatch_get_main_queue(), ^{
                //切换到主线程 执行任务
                //若主线程没有出现卡顿 能正常执行任务 将timeOut设置为NO
                //若主线程出现卡顿 不能能正常执行任务
               timeOut = NO;
                //发送信号量 +1
               dispatch_semaphore_signal(SHAREDMONITOR.eventSemphore);
               NSLog(@"1");
            });
            
            NSLog(@"2");
            //当前子线程休眠1秒钟
            [NSThread sleepForTimeInterval: lxd_time_out_interval];
            NSLog(@"3");
            //超时打印函数调用栈
            if (timeOut) {
               NSLog(@"4");
               [LXDBacktraceLogger lxd_logMain];
            }
            NSLog(@"5");
            //释放信号量 -1 此时的信号量为-1<0 下面的逻辑不会执行 循环依然执行
            dispatch_wait(SHAREDMONITOR.eventSemphore, DISPATCH_TIME_FOREVER);
            NSLog(@"6");
        }
    }
});
image.png
卡顿监测的第三种方案:使用Instrument工具实时监测App
image.png image.png image.png image.png image.png
卡顿的优化
CPU的资源消耗与解决方案
NSArray *tmp = self.array;
self.array = nil;
dispatch_async(queue, ^{
    [tmp class];
});
- (void)display {
    dispatch_async(backgroundQueue, ^{
        CGContextRef ctx = CGBitmapContextCreate(...);
        // draw in context...
        CGImageRef img = CGBitmapContextCreateImage(ctx);
        CFRelease(ctx);
        dispatch_async(mainQueue, ^{
            layer.contents = img;
        });
    });
}
GPU的资源消耗与解决方案
离屏渲染
圆角图片引发离屏渲染的探索
image.png image.png image.png
毛玻璃效果会引发离屏渲染
image.png
阴影效果会引发离屏渲染
image.png
遮罩效果会引发离屏渲染
image.png

参考文章如下:
iOS开发优化篇之卡顿检测
iOS卡顿监测方案总结
iOS 保持界面流畅的技巧
iOS应用千万级架构:性能优化与卡顿监控
iOS 性能优化总结
IOS面试考察(九):性能优化相关问题
iOS圆角的离屏渲染,你真的弄明白了吗
iOS 渲染原理解析
iOS-底层原理39-离屏渲染
深入剖析【离屏渲染】原理

上一篇下一篇

猜你喜欢

热点阅读