iOS-卡顿简单监测二(NSTimer 实现+附实例)

480 阅读14分钟
原文链接: www.jianshu.com
序言

之前写了一篇文章介绍如何检测卡顿,iOS实时卡顿检测-RunLoop(附实例)这是借助于信号量Semaphore来实现的。本介绍第二种方法,采用定时器 NSTimer实现,原理方面的就不多说了,看之前一篇文章即可,直接上代码。

一 监测的工具类Monitor
  • 工具类 Monitor.h
/**
 卡顿监控工具类
 */
@interface Monitor : NSObject

/// 单例
+ (instancetype)shareInstance;

/// 开启卡顿监听
- (void)startMonitor;

/// 停止监听
- (void)endMonitor;

/// 打印堆栈信息
- (void)printTraceLog;

@end
  • Monitor.m
#import "Monitor.h"
#import <objc/runtime.h>
#import <CrashReporter/CrashReporter.h>

static double _waitStartTime;   // 等待启动的时间

@implementation Monitor {
    CFRunLoopObserverRef _observer; // runloop observer
    double _lastRecordTime; // last record time
    NSMutableArray *_backtraces;
}

+ (instancetype)shareInstance {
    static dispatch_once_t onceToken;
    static id shareInstance;
    dispatch_once(&onceToken, ^{
        shareInstance = [[self alloc] init];
    });
    return shareInstance;
}

#pragma mark - start | end

- (void)startMonitor {
    [self addMainThreadObserver];
    [self addSecondaryThreadAndObserver];
}

- (void)endMonitor {
    if (!_observer) {
        return;
    }
    CFRunLoopRemoveObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
    CFRelease(_observer);
    _observer = NULL;
}

#pragma mark - MainThread runloop observer

/// 添加在主线程的 runloop 监听器
- (void)addMainThreadObserver {
    dispatch_async(dispatch_get_main_queue(), ^{
        // 建立自动释放池
        @autoreleasepool {
            // 获得当前线程的 runloop
            NSRunLoop *mainRunLoop = [NSRunLoop currentRunLoop];
            
            // 设置runloop observer 的运行环境
            /** 第一个参数用于分配observer对象的内存
                第二个参数用以设置observer所要关注的事件,详见回调函数myRunLoopObserver中注释
                第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行
                第四个参数用于设置该observer的优先级
                第五个参数用于设置该observer的回调函数
                第六个参数用于设置该observer的运行环境 */
            CFRunLoopObserverContext context =  {0, (__bridge void *)(self), NULL, NULL, NULL};
            
            // 创建 runloop observer 对象
            self->_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &mainRunLoopObserver, &context);
            
            if (self->_observer) {
                // 将 cocoa的 NSRunLoop 类型转换为 Core Foundation 的 CFRunLoopRef 类型
                CFRunLoopRef cfRunLoop = [mainRunLoop getCFRunLoop];
                // 将新建的 observer 加入到当前 thread 的 runloop 中
                CFRunLoopAddObserver(cfRunLoop, self->_observer, kCFRunLoopDefaultMode);
            }
        }
    });

}

void mainRunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss:SSS"];
    NSString *time = [formatter stringFromDate:[NSDate date]];
    
    switch (activity) {
            //The entrance of the run loop, before entering the event processing loop.
            //This activity occurs once for each call to CFRunLoopRun and CFRunLoopRunInMode
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry - %@",time);
            break;
            //Inside the event processing loop before any timers are processed
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers - %@",time);
            break;
            //Inside the event processing loop before any sources are processed
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources - %@",time);
            break;
            //Inside the event processing loop before the run loop sleeps, waiting for a source or timer to fire.
            //This activity does not occur if CFRunLoopRunInMode is called with a timeout of 0 seconds.
            //It also does not occur in a particular iteration of the event processing loop if a version 0 source fires
        case kCFRunLoopBeforeWaiting:{  // 即将进入休眠-这个时候处理 UI 操作
            _waitStartTime = 0;
            NSLog(@"kCFRunLoopBeforeWaiting - %@",time);
            break;
        }
            //Inside the event processing loop after the run loop wakes up, but before processing the event that woke it up.
            //This activity occurs only if the run loop did in fact go to sleep during the current loop
        case kCFRunLoopAfterWaiting:{   // 从休眠中醒来开始做事情了
            _waitStartTime = [[NSDate date] timeIntervalSince1970];
            NSLog(@"kCFRunLoopAfterWaiting - %@",time);
            break;
        }
            //The exit of the run loop, after exiting the event processing loop.
            //This activity occurs once for each call to CFRunLoopRun and CFRunLoopRunInMode
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit - %@",time);
            break;
            /*
             A combination of all the preceding stages
             case kCFRunLoopAllActivities:
             break;
             */
        default:
            break;
    }
}

#pragma mark - second thread observer

- (void)addSecondaryThreadAndObserver {
    NSThread *thread = [self secondaryThread];
    [self performSelector:@selector(addSecondaryTimer) onThread:thread withObject:nil waitUntilDone:YES];
}

#pragma mark - timer

- (void)addSecondaryTimer {
    __weak typeof(self)weakSelf = self;
    NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 repeats:YES block:^(NSTimer *timer) {
        [weakSelf timerFired];
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}

- (void)timerFired {
    if (_waitStartTime < 1) {   // 因为刚刚经历了kCFRunLoopBeforeWaiting状态,_waitStartTime=0,直接 pass
        NSLog(@"timerFired return curTime:%@, waitStartTime:%@",[self getCurTimeStamp], [self getTimeStamp:_waitStartTime]);
        return;
    }

    double currentTime = [[NSDate date] timeIntervalSince1970];
    double timeDiff = currentTime - _waitStartTime;

    NSLog(@"timerFired curTime:%@, waitStartTime:%@, timeDiff:%f, _lastRecordTime:%f",[self getCurTimeStamp], [self getTimeStamp:_waitStartTime], timeDiff, _lastRecordTime);

    // 如果 timeDiff 时间间隔超过 2S,表示 runloop 处于kCFRunLoopAfterWaiting跟kCFRunLoopBeforeWaiting之间状态很长时间
    // 即长时间处于kCFRunLoopBeforeTimers和kCFRunLoopBeforeSources状态,就是进行 UI 操作了
    if (timeDiff > 2.0) {
        NSLog(@"last lastRecordTime:%f waitStartTime:%f",_lastRecordTime,_waitStartTime);
        if (_lastRecordTime - _waitStartTime < 0.001 && _lastRecordTime != 0) { // 距离上一次记录堆栈信息时间过短的话,就直接 pass,避免短时间内多次记录堆栈信息
            NSLog(@"last return timeDiff:%f waitStartTime:%@ lastRecordTime:%@ difference:%f",timeDiff, [self getTimeStamp:_waitStartTime], [self getTimeStamp:_lastRecordTime], _lastRecordTime - _waitStartTime);
            return;
        }
        NSLog(@"记录崩溃堆栈信息");
        [self logStack];
        _lastRecordTime = _waitStartTime;
    }
}

#pragma mark - stack

- (void)printTraceLog {
    
}

- (void)logStack {
    // 收集Crash信息也可用于实时获取各线程的调用堆栈
    PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll];
    
    PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config];
    
    NSData *data = [crashReporter generateLiveReport];
    PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL];
    NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter withTextFormat:PLCrashReportTextFormatiOS];
    
    NSLog(@"---------卡顿信息\n%@\n--------------",report);
}

#pragma mark - private

/// 返回一个子线程
- (NSThread *)secondaryThread {
    static NSThread *_secondaryThread = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _secondaryThread = [[NSThread alloc] initWithTarget:self
                                                   selector:@selector(networkRequestThreadEntryPoint:)
                                                     object:nil];
        [_secondaryThread start];
    });
    return _secondaryThread;
}

- (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"MonitorThread"];
        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        [runloop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
        [runloop run];
    }
}

- (NSString *)getCurTimeStamp {
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss:SSS"];
    return [formatter stringFromDate:[NSDate date]];
}

- (NSString *)getTimeStamp:(double)time {
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:time];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss:SSS"];
    return [formatter stringFromDate:date];
}

@end
二 核心代码解析
2.1 实现思路

首先在主线程注册了runloop observer的回调mainRunLoopObserver,每次当runloop的状态发生变更的时候,该方法都会回调一次。

拿一个变量_waitStartTime记录runloop从休眠中唤醒时的时间,即当runloop处于kCFRunLoopAfterWaiting状态时,记录runloop唤醒时的时间。当runloop即将进入休眠时,即处于kCFRunLoopBeforeWaiting状态时,将该变量置为零。

另外开一个子线程并且开启runloop(模仿AFNetworking的方式),然后每隔0.5秒去检测,即对比一下时间,如果发现当前时间与_waitStartTime差距大于 2S,则可知道 runloop 处于kCFRunLoopAfterWaitingkCFRunLoopBeforeWaiting之间状态很长时间,则认为有卡顿情况,记录当前堆栈信息。

本文借助 PLCrashReporter来获取所有线程对线信息。

接下来对较难理解的代码进行详细讲解

2.2 _waitStartTime变量讲解
case kCFRunLoopBeforeWaiting:{  // 即将进入休眠-这个时候处理 UI 操作
    _waitStartTime = 0;
    NSLog(@"kCFRunLoopBeforeWaiting - %@",time);
    break;
}
    //Inside the event processing loop after the run loop wakes up, but before processing the event that woke it up.
    //This activity occurs only if the run loop did in fact go to sleep during the current loop
case kCFRunLoopAfterWaiting:{   // 从休眠中醒来开始做事情了
    _waitStartTime = [[NSDate date] timeIntervalSince1970];
    NSLog(@"kCFRunLoopAfterWaiting - %@",time);
    break;
}

因为我们知道UI的操作是在runloop处于kCFRunLoopBeforeWaiting之前,即处于kCFRunLoopBeforeTimers或者kCFRunLoopBeforeSources状态。所以当runloop处于kCFRunLoopAfterWaiting的时候,记录唤醒的时间,当处于kCFRunLoopBeforeWaiting时,将时间清零。

运行结果如下

2019-06-29 15:57:26.962881+0800 MonitorTimer[98651:1479432] kCFRunLoopBeforeWaiting - 2019-06-29 15:57:26:963
2019-06-29 15:57:27.090253+0800 MonitorTimer[98651:1479561] timerFired return curTime:2019-06-29 15:57:27:090, waitStartTime:1970-01-01 08:00:00:000
...
2019-06-29 15:57:27.377416+0800 MonitorTimer[98651:1479432] kCFRunLoopBeforeWaiting - 2019-06-29 15:57:27:377
2019-06-29 15:57:27.591108+0800 MonitorTimer[98651:1479561] timerFired return curTime:2019-06-29 15:57:27:591, waitStartTime:1970-01-01 08:00:00:000
...
2019-06-29 15:57:39.524085+0800 MonitorTimer[98651:1479432] kCFRunLoopBeforeWaiting - 2019-06-29 15:57:39:524
2019-06-29 15:57:39.590825+0800 MonitorTimer[98651:1479561] timerFired return curTime:2019-06-29 15:57:39:590, waitStartTime:1970-01-01 08:00:00:000
...
2019-06-29 15:57:39.823567+0800 MonitorTimer[98651:1479432] kCFRunLoopBeforeWaiting - 2019-06-29 15:57:39:823
2019-06-29 15:57:40.093783+0800 MonitorTimer[98651:1479561] timerFired return curTime:2019-06-29 15:57:40:093, waitStartTime:1970-01-01 08:00:00:000
2.3 timerFired方法讲解(也是最重要最难理解的方法)
- (void)timerFired {
    if (_waitStartTime < 1) {   // 因为刚刚经历了kCFRunLoopBeforeWaiting状态,_waitStartTime=0,直接 pass
        NSLog(@"timerFired return curTime:%@, waitStartTime:%@",[self getCurTimeStamp], [self getTimeStamp:_waitStartTime]);
        return;
    }

    double currentTime = [[NSDate date] timeIntervalSince1970];
    double timeDiff = currentTime - _waitStartTime;

    NSLog(@"timerFired curTime:%@, waitStartTime:%@, timeDiff:%f, _lastRecordTime:%f",[self getCurTimeStamp], [self getTimeStamp:_waitStartTime], timeDiff, _lastRecordTime);

    // 如果 timeDiff 时间间隔超过 2S,表示 runloop 处于kCFRunLoopAfterWaiting跟kCFRunLoopBeforeWaiting之间状态很长时间
    // 即长时间处于kCFRunLoopBeforeTimers和kCFRunLoopBeforeSources状态,就是进行 UI 操作了
    if (timeDiff > 2.0) {
        NSLog(@"last lastRecordTime:%f waitStartTime:%f",_lastRecordTime,_waitStartTime);
        if (_lastRecordTime - _waitStartTime < 0.001 && _lastRecordTime != 0) { // 距离上一次记录堆栈信息时间过短的话,就直接 pass,避免短时间内多次记录堆栈信息
            NSLog(@"last return timeDiff:%f waitStartTime:%@ lastRecordTime:%@ difference:%f",timeDiff, [self getTimeStamp:_waitStartTime], [self getTimeStamp:_lastRecordTime], _lastRecordTime - _waitStartTime);
            return;
        }
        NSLog(@"记录崩溃堆栈信息");
        [self logStack];
        _lastRecordTime = _waitStartTime;
    }
}
2.3.1 _waitStartTime < 1直接return
if (_waitStartTime < 1) {   // 因为刚刚经历了kCFRunLoopBeforeWaiting状态,_waitStartTime=0,直接 pass
    NSLog(@"timerFired return curTime:%@, waitStartTime:%@",[self getCurTimeStamp], [self getTimeStamp:_waitStartTime]);
    return;
}

前面说了,当runloop处于kCFRunLoopAfterWaiting时,记录当前唤醒的时间,当处于kCFRunLoopBeforeWaiting时,将_waitStartTime清零。也就是说当runloop处于kCFRunLoopAfterWaitingkCFRunLoopBeforeWaiting之间时,直接 pass,不需要做事情。

运行结果如下

2019-06-29 15:57:26.962881+0800 MonitorTimer[98651:1479432] kCFRunLoopBeforeWaiting - 2019-06-29 15:57:26:963
2019-06-29 15:57:27.090253+0800 MonitorTimer[98651:1479561] timerFired return curTime:2019-06-29 15:57:27:090, waitStartTime:1970-01-01 08:00:00:000
...
2019-06-29 15:57:27.377416+0800 MonitorTimer[98651:1479432] kCFRunLoopBeforeWaiting - 2019-06-29 15:57:27:377
2019-06-29 15:57:27.591108+0800 MonitorTimer[98651:1479561] timerFired return curTime:2019-06-29 15:57:27:591, waitStartTime:1970-01-01 08:00:00:000
...
2019-06-29 15:57:39.524085+0800 MonitorTimer[98651:1479432] kCFRunLoopBeforeWaiting - 2019-06-29 15:57:39:524
2019-06-29 15:57:39.590825+0800 MonitorTimer[98651:1479561] timerFired return curTime:2019-06-29 15:57:39:590, waitStartTime:1970-01-01 08:00:00:000
...
2019-06-29 15:57:39.823567+0800 MonitorTimer[98651:1479432] kCFRunLoopBeforeWaiting - 2019-06-29 15:57:39:823
2019-06-29 15:57:40.093783+0800 MonitorTimer[98651:1479561] timerFired return curTime:2019-06-29 15:57:40:093, waitStartTime:1970-01-01 08:00:00:000
2.3.2 如何判断出现卡顿
double currentTime = [[NSDate date] timeIntervalSince1970];
double timeDiff = currentTime - _waitStartTime;

if (timeDiff > 2.0) {
    // 出现了卡顿
}

runloop处于kCFRunLoopAfterWaiting时,记录当前唤醒的时间,当处于kCFRunLoopBeforeWaiting时,将_waitStartTime清零。如果 runloop唤醒后,长时间处于kCFRunLoopAfterWaiting之前的状态,即处于kCFRunLoopBeforeTimers或者kCFRunLoopBeforeSources状态,表示正在做很多事情,即出现了卡顿。导致_waitStartTime的值一直不变,然后timeDiff的值越来越大,当达到临界值2S时,即可判断出现了卡顿现象。

运行结果如下

019-06-29 15:57:28.089722+0800 MonitorTimer[98651:1479561] timerFired curTime:2019-06-29 15:57:28:089, waitStartTime:2019-06-29 15:57:27:743, timeDiff:0.345903, _lastRecordTime:0.000000
2019-06-29 15:57:28.591188+0800 MonitorTimer[98651:1479561] timerFired curTime:2019-06-29 15:57:28:591, waitStartTime:2019-06-29 15:57:27:743, timeDiff:0.847366, _lastRecordTime:0.000000
2019-06-29 15:57:29.089330+0800 MonitorTimer[98651:1479561] timerFired curTime:2019-06-29 15:57:29:089, waitStartTime:2019-06-29 15:57:27:743, timeDiff:1.345805, _lastRecordTime:0.000000
2019-06-29 15:57:29.589632+0800 MonitorTimer[98651:1479561] timerFired curTime:2019-06-29 15:57:29:588, waitStartTime:2019-06-29 15:57:27:743, timeDiff:1.845115, _lastRecordTime:0.000000
2019-06-29 15:57:30.089099+0800 MonitorTimer[98651:1479561] timerFired curTime:2019-06-29 15:57:30:089, waitStartTime:2019-06-29 15:57:27:743, timeDiff:2.345361, _lastRecordTime:0.000000
2019-06-29 15:57:30.089665+0800 MonitorTimer[98651:1479561] last lastRecordTime:0.000000 waitStartTime:1561795047.743299
2019-06-29 15:57:30.089864+0800 MonitorTimer[98651:1479561] 记录崩溃堆栈信息
2.3.3 如何防止重复多次记录卡顿信息
if (_lastRecordTime - _waitStartTime < 0.001 && _lastRecordTime != 0) { 
    // 距离上一次记录堆栈信息时间过短的话,就直接 pass,避免短时间内多次记录堆栈信息
    return;
}
NSLog(@"记录崩溃堆栈信息");
[self logStack];
_lastRecordTime = _waitStartTime;

因为如果runloop长时间处于kCFRunLoopBeforeTimers或者kCFRunLoopBeforeSources状态,表示正在做很多事情,即出现了卡顿。然后_lastRecordTime的值和_waitStartTime值是相同的。所以这样就会直接return,不会记录卡顿堆栈信息了。

只有当上一次记录堆栈信息时,_waitStartTime刚好为 0,导致_lastRecordTime也为零。或者_waitStartTime的值为零,则满足记录卡顿对线信息的条件。

运行结果如下

...
2019-06-29 15:57:14.088766+0800 MonitorTimer[98651:1479561] last lastRecordTime:0.000000 waitStartTime:1561795031.917216
2019-06-29 15:57:14.089345+0800 MonitorTimer[98651:1479561] 记录崩溃堆栈信息
...
2019-06-29 15:57:24.600888+0800 MonitorTimer[98651:1479561] last lastRecordTime:1561795031.917216 waitStartTime:0.000000
2019-06-29 15:57:24.601743+0800 MonitorTimer[98651:1479561] 记录崩溃堆栈信息
...
2019-06-29 15:57:30.089665+0800 MonitorTimer[98651:1479561] last lastRecordTime:0.000000 waitStartTime:1561795047.743299
2019-06-29 15:57:30.089864+0800 MonitorTimer[98651:1479561] 记录崩溃堆栈信息
...
2.3.4 如何记录堆栈信息
- (void)logStack {
    // 收集Crash信息也可用于实时获取各线程的调用堆栈
    PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll];
    
    PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config];
    
    NSData *data = [crashReporter generateLiveReport];
    PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL];
    NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter withTextFormat:PLCrashReportTextFormatiOS];
    
    NSLog(@"---------卡顿信息\n%@\n--------------",report);
}

借助 PLCrashReporter来获取所有线程对线信息。

运行结果如下

2019-06-29 15:57:14.089345+0800 MonitorTimer[98651:1479561] 记录崩溃堆栈信息
2019-06-29 15:57:15.066679+0800 MonitorTimer[98651:1479561] ---------卡顿信息
Incident Identifier: 0A37B895-4801-44EB-831D-3DF6E5D9E675
CrashReporter Key:   TODO
Hardware Model:      x86_64
Process:         MonitorTimer [98651]
Path:            /Users/cs/Library/Developer/CoreSimulator/Devices/2BAC277B-4BE9-4769-B3E0-12B8177803F9/data/Containers/Bundle/Application/B7DA98D5-D8DF-4006-88C3-2114BC6D0652/MonitorTimer.app/MonitorTimer
Identifier:      cs.MonitorTimer
Version:         1.0 (1)
Code Type:       X86-64
Parent Process:  debugserver [98652]

Date/Time:       2019-06-29 07:57:14 +0000
OS Version:      Mac OS X 12.2 (18F132)
Report Version:  104

Exception Type:  SIGTRAP
Exception Codes: TRAP_TRACE at 0x10b478ccf
Crashed Thread:  9

Thread 0:
0   libsystem_kernel.dylib              0x000000010e3e822a mach_msg_trap + 10
1   CoreFoundation                      0x000000010c752684 __CFRunLoopServiceMachPort + 212
2   CoreFoundation                      0x000000010c74ccc9 __CFRunLoopRun + 1657
3   CoreFoundation                      0x000000010c74c302 CFRunLoopRun

不过貌似没有记录到有用的堆栈信息,之前的文章是有记录到的 iOS实时卡顿检测-RunLoop(附实例),具体原因,要再研究一下,有结果后会再次更新文章。

2.3.5 测试代码
#import "ViewController.h"
#import "Monitor.h"

@interface ViewController ()<UITableViewDataSource> {
    UITableView *_tableView;
}

@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
     [self drawTableView];
    
    // 开始卡顿监听
    [[Monitor shareInstance] startMonitor];
}

/// 绘制TableView视图
- (void)drawTableView {
    _tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    _tableView.dataSource = self;
    [self.view addSubview:_tableView];
}

#pragma mark - UITableViewDataSource

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }
    
    NSString *text = nil;
    
    if (indexPath.row % 10 == 0) {  // 每10行休眠0.2S
        usleep(500 * 1000); // 1 * 1000 * 1000 == 1秒
        text = @"我在做复杂的事情,需要一些时间";
    } else {
        text = [NSString stringWithFormat:@"cell - %ld",indexPath.row];
    }
    
    cell.textLabel.text = text;
    
    return cell;
}

本文参考 简单监测iOS卡顿的demo,非常感谢该作者。


相关文章参考


项目链接地址 - MonitorTimer