Detección de congelación en tiempo real de iOS-RunLoop (con ejemplos)

Prefacio

Al desarrollar software en dispositivos móviles, el rendimiento siempre ha sido uno de nuestros temas más preocupados. Además de trabajar duro para mejorar la calidad del código como programadores, también es nuestro sagrado descubrir y monitorear a los "culpables" del software que causan un rendimiento deficiente a tiempo. Responsabilidad.

Como todos sabemos, debido a las características de UIKit en sí, la plataforma iOS necesita colocar todas las operaciones de la interfaz de usuario en el hilo principal para su ejecución, por lo que muchos programadores están acostumbrados a alguna lógica con seguridad de hilos incierta, y el trabajo de resumen después del final de otros hilos. Esperando en el hilo principal, por lo que la gran cantidad de cálculo, IO y dibujo contenido en el hilo principal puede causar un retraso.

Una herramienta de depuración muy conveniente, Instruments, se ha integrado en Xcode. Puede ayudarnos a analizar el consumo de rendimiento del software durante la fase de desarrollo y prueba. Sin embargo, definitivamente no es suficiente que un software pase por el proceso de prueba y el análisis de laboratorio. Los datos monitoreados y analizados por el usuario durante el proceso de uso pueden resolver mejor algunos problemas ocultos.

¿Qué causó el retraso?

  • Punto muerto
  • Agarra la cerradura
  • Muchos dibujos de UI, UI complejas, gráficos y texto mixtos
  • Mucho IO y muchos cálculos en el hilo principal

Suplemento-semáforo de conocimientos relacionados

El semáforo es un contador de recursos Hay dos operaciones en el semáforo para lograr la exclusión mutua, a saber, las operaciones P y V. La situación general es realizar acceso crítico o acceso exclusivo de la siguiente manera: Establecer el valor del semáforo en 1, cuando un proceso 1 está en ejecución, utilizar el recurso para realizar la operación P, es decir, reducir el valor del semáforo en 1, es decir, el número de recursos es uno menos , El valor del semáforo es 0 en este momento.

El sistema estipula que cuando el valor del semáforo es 0, debe esperar hasta que el valor del semáforo no sea cero para continuar la operación. En este momento, si el proceso 2 quiere ejecutarse, entonces se debe realizar la operación P, pero en este momento el semáforo es 0, por lo que no se puede reducir en 1, es decir, la operación P no se puede realizar, y está bloqueada, por lo que el proceso 1 tiene acceso exclusivo.

Cuando finaliza el proceso 1, se liberan los recursos y se realiza la operación V. El número de recursos se incrementa en 1 y el valor del semáforo se convierte en 1. En este momento, el proceso 2 encuentra que el número de recursos no es 0 y el semáforo puede realizar la operación P e inmediatamente ejecutar la operación P. El valor del semáforo vuelve a ser 0. En este momento, el proceso 2 tiene recursos y tiene acceso exclusivo a los recursos. Este es el principio de semáforo para controlar la exclusión mutua.

Dificultades de tartamudeo

  • 不易重现
    Puede ser el problema en el teléfono móvil de un usuario específico, que no se puede usar para depurar debido a varias razones; o puede ser un problema en un momento específico y no se puede reproducir más tarde (como la captura de hilo).

  • 操作路径长,日志无法准确打点
    Para esta retroalimentación rezagada en la interfaz, usualmente usamos registros de usuario para que sean de poca utilidad, y agregar puntos de registro también es de poca utilidad. Solo puedo seguir intentándolo y esperar reproducirlo, o enterrar mi cabeza en las pistas que puedo encontrar en la lógica del código.

Encontrar el punto de entrada para el rezago

La forma más directa de monitorear el retraso es averiguar qué está haciendo el hilo principal. Sabemos que el procesamiento de eventos de mensaje de un hilo lo controla NSRunLoop, por lo que para saber a qué método está llamando el hilo, debe comenzar con NSRunLoop. El código de CFRunLoop es de código abierto, puede consultar el código fuente de CFRunLoop.c aquí

La lógica principal del método básico simplificado CFRunLoopRun es probablemente la siguiente:

/// 1. 通知Observers,即将进入RunLoop
    /// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush();
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
    do {
 
        /// 2. 通知 Observers: 即将触发 Timer 回调。
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
        /// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
 
        /// 4. 触发 Source0 (非基于port的) 回调。
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);

        /// 5. GCD处理main block
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
 
        /// 6. 通知Observers,即将进入休眠
        /// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
 
        /// 7. sleep to wait msg.
        mach_msg() -> mach_msg_trap();
        
 
        /// 8. 通知Observers,线程被唤醒
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
 
        /// 9. 如果是被Timer唤醒的,回调Timer
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
 
        /// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
        __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
 
        /// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
 
 
    } while (...);
 
    /// 10. 通知Observers,即将退出RunLoop
    /// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}

No es difícil encontrar que el método de llamada de NSRunLoop es principalmente entre kCFRunLoopBeforeSources y kCFRunLoopBeforeWaiting, y después de kCFRunLoopAfterWaiting, es decir, si encontramos que estos dos tiempos toman demasiado tiempo, entonces podemos determinar que el hilo principal está atascado en este momento.

Cuantificar el grado de retraso

Para monitorear el estado de NSRunLoop, necesitamos usar CFRunLoopObserverRef, que puede obtener los cambios de estos valores de estado en tiempo real. El uso específico es el siguiente:

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    MyClass *object = (__bridge MyClass*)info;
    object->activity = activity;
}

- (void)registerObserver
{
    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                                            kCFRunLoopAllActivities,
                                                            YES,
                                                            0,
                                                            &runLoopObserverCallBack,
                                                            &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}

La interfaz de usuario se concentra principalmente en __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
y __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1); antes.
Llegar kCFRunLoopBeforeSourcesal estado kCFRunLoopBeforeWaitingentonces kCFRunLoopAfterWaitingpuede saber si ha habido Caton.

实现思路:只需要另外再开启一个线程,实时计算这两个状态区域之间的耗时是否到达某个阀值,便能揪出这些性能杀手。

  • Supervisar runloopcambios de estado
// 就是runloop有一个状态改变 就记录一下
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    BGPerformanceMonitor *monitor = (__bridge BGPerformanceMonitor*)info;
    
    // 记录状态值
    monitor->activity = activity;
    
    // 发送信号
    dispatch_semaphore_t semaphore = monitor->semaphore;
    long st = dispatch_semaphore_signal(semaphore);
    NSLog(@"dispatch_semaphore_signal:st=%ld,time:%@",st,[BGPerformanceMonitor getCurTime]);
    
    /* Run Loop Observer Activities */
//    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
//        kCFRunLoopEntry = (1UL << 0),    // 进入RunLoop循环(这里其实还没进入)
//        kCFRunLoopBeforeTimers = (1UL << 1),  // RunLoop 要处理timer了
//        kCFRunLoopBeforeSources = (1UL << 2), // RunLoop 要处理source了
//        kCFRunLoopBeforeWaiting = (1UL << 5), // RunLoop要休眠了
//        kCFRunLoopAfterWaiting = (1UL << 6),   // RunLoop醒了
//        kCFRunLoopExit = (1UL << 7),           // RunLoop退出(和kCFRunLoopEntry对应)
//        kCFRunLoopAllActivities = 0x0FFFFFFFU
//    };
    
    if (activity == kCFRunLoopEntry) {  // 即将进入RunLoop
        NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopEntry");
    } else if (activity == kCFRunLoopBeforeTimers) {    // 即将处理Timer
        NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeTimers");
    } else if (activity == kCFRunLoopBeforeSources) {   // 即将处理Source
        NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeSources");
    } else if (activity == kCFRunLoopBeforeWaiting) {   //即将进入休眠
        NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeWaiting");
    } else if (activity == kCFRunLoopAfterWaiting) {    // 刚从休眠中唤醒
        NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopAfterWaiting");
    } else if (activity == kCFRunLoopExit) {    // 即将退出RunLoop
        NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopExit");
    } else if (activity == kCFRunLoopAllActivities) {
        NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopAllActivities");
    }
}
  • runloopEscucha abierta
// 开始监听
- (void)startMonitor {
    if (observer) {
        return;
    }
    
    // 创建信号
    semaphore = dispatch_semaphore_create(0);
    NSLog(@"dispatch_semaphore_create:%@",[BGPerformanceMonitor getCurTime]);
    
    // 注册RunLoop状态观察
    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    //创建Run loop observer对象
    //第一个参数用于分配observer对象的内存
    //第二个参数用以设置observer所要关注的事件,详见回调函数myRunLoopObserver中注释
    //第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行
    //第四个参数用于设置该observer的优先级
    //第五个参数用于设置该observer的回调函数
    //第六个参数用于设置该observer的运行环境
    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                       kCFRunLoopAllActivities,
                                       YES,
                                       0,
                                       &runLoopObserverCallBack,
                                       &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    
    // 在子线程监控时长
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (YES) {   // 有信号的话 就查询当前runloop的状态
            // 假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms)
            // 因为下面 runloop 状态改变回调方法runLoopObserverCallBack中会将信号量递增 1,所以每次 runloop 状态改变后,下面的语句都会执行一次
            // dispatch_semaphore_wait:Returns zero on success, or non-zero if the timeout occurred.
            long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
            NSLog(@"dispatch_semaphore_wait:st=%ld,time:%@",st,[self getCurTime]);
            if (st != 0) {  // 信号量超时了 - 即 runloop 的状态长时间没有发生变更,长期处于某一个状态下
                if (!observer) {
                    timeoutCount = 0;
                    semaphore = 0;
                    activity = 0;
                    return;
                }
                NSLog(@"st = %ld,activity = %lu,timeoutCount = %d,time:%@",st,activity,timeoutCount,[self getCurTime]);
                // kCFRunLoopBeforeSources - 即将处理source kCFRunLoopAfterWaiting - 刚从休眠中唤醒
                // 获取kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting的状态就可以知道是否有卡顿的情况。
                // kCFRunLoopBeforeSources:停留在这个状态,表示在做很多事情
                if (activity == kCFRunLoopBeforeSources || activity == kCFRunLoopAfterWaiting) {    // 发生卡顿,记录卡顿次数
                    if (++timeoutCount < 5) {
                        continue;   // 不足 5 次,直接 continue 当次循环,不将timeoutCount置为0
                    }
                    
                    // 收集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);
                }
            }
            NSLog(@"dispatch_semaphore_wait timeoutCount = 0,time:%@",[self getCurTime]);
            timeoutCount = 0;
        }
    });
}

Grabar llamadas a funciones atascadas

Después de monitorear el sitio estancado, por supuesto, el siguiente paso es registrar la información de la llamada a la función en este momento. Aquí, se puede usar un componente de recopilación de Crash de terceros, PLCrashReporter. No solo puede recopilar información de Crash, sino que también puede usarse para obtener la pila de llamadas de cada hilo en tiempo real. como sigue:

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);

Cuando se detecta un tartamudeo, tome la información de la pila y luego filtre un poco en el cliente y luego informe al servidor. Después de recopilar una cierta cantidad de datos de tartamudeo y analizarlos, la lógica que debe optimizarse se puede ubicar con precisión. Hasta ahora, esta tarjeta en tiempo real Después de monitorear, ¡ya está!

Caso de prueba

Escriba una vista tableView, arrastre hacia arriba y hacia abajo, y configure artificialmente la congelación (suspensión)

- (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;
}

Ejecute el resultado uno

2019-06-27 19:22:35.898214+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopAfterWaiting
2019-06-27 19:22:35.899001+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:35:899
2019-06-27 19:22:35.899395+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopBeforeTimers
2019-06-27 19:22:35.900216+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:35:900
2019-06-27 19:22:35.900425+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopBeforeSources
2019-06-27 19:22:35.903171+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:35:903
2019-06-27 19:22:35.903317+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopBeforeTimers
2019-06-27 19:22:35.903563+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:35:903
2019-06-27 19:22:35.903722+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopBeforeSources
2019-06-27 19:22:35.904127+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:35:904
2019-06-27 19:22:35.904372+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopBeforeWaiting
2019-06-27 19:22:35.901860+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait timeoutCount = 0,time:2019/06/27 07:22:35:898
2019-06-27 19:22:35.906158+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait:st=0,time:2019/06/27 07:22:35:906
2019-06-27 19:22:35.914693+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:35:914
2019-06-27 19:22:35.915027+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopAfterWaiting
2019-06-27 19:22:35.917535+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:35:916
2019-06-27 19:22:35.918352+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopBeforeTimers
2019-06-27 19:22:35.919467+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:35:919
2019-06-27 19:22:35.919853+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopBeforeSources
2019-06-27 19:22:35.920694+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:35:920
2019-06-27 19:22:35.921004+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopBeforeWaiting
2019-06-27 19:22:35.925240+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:35:925
2019-06-27 19:22:35.925425+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopAfterWaiting
2019-06-27 19:22:35.925699+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:35:926
2019-06-27 19:22:35.925814+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopBeforeTimers
2019-06-27 19:22:35.926058+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:35:926
2019-06-27 19:22:35.926264+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopBeforeSources

Ejecutar un resultado: por impresión, sabemos que runloopel estado de funcionamiento, respectivamente, con experiencia kCFRunLoopAfterWaiting,, kCFRunLoopBeforeTimers, kCFRunLoopBeforeSources, kCFRunLoopBeforeWaiting, kCFRunLoopAfterWaitingque runloopel modo de operación

Resultado de ejecución dos

2019-06-27 19:22:36.031643+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait:st=0,time:2019/06/27 07:22:36:032
2019-06-27 19:22:36.032004+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait timeoutCount = 0,time:2019/06/27 07:22:36:032
2019-06-27 19:22:36.032478+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait:st=0,time:2019/06/27 07:22:36:032
2019-06-27 19:22:36.032681+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait timeoutCount = 0,time:2019/06/27 07:22:36:033
2019-06-27 19:22:36.084247+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait:st=49,time:2019/06/27 07:22:36:084
2019-06-27 19:22:36.084560+0800 1129_PerformanceMonitor[66196:709130] st = 49,activity = 4,timeoutCount = 0,time:2019/06/27 07:22:36:084
2019-06-27 19:22:36.139575+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait:st=49,time:2019/06/27 07:22:36:139
2019-06-27 19:22:36.139918+0800 1129_PerformanceMonitor[66196:709130] st = 49,activity = 4,timeoutCount = 1,time:2019/06/27 07:22:36:140
2019-06-27 19:22:36.191970+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait:st=49,time:2019/06/27 07:22:36:192
2019-06-27 19:22:36.192274+0800 1129_PerformanceMonitor[66196:709130] st = 49,activity = 4,timeoutCount = 2,time:2019/06/27 07:22:36:192
2019-06-27 19:22:36.244942+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait:st=49,time:2019/06/27 07:22:36:245
2019-06-27 19:22:36.245238+0800 1129_PerformanceMonitor[66196:709130] st = 49,activity = 4,timeoutCount = 3,time:2019/06/27 07:22:36:245
2019-06-27 19:22:36.295878+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait:st=49,time:2019/06/27 07:22:36:296
2019-06-27 19:22:36.296166+0800 1129_PerformanceMonitor[66196:709130] st = 49,activity = 4,timeoutCount = 4,time:2019/06/27 07:22:36:296
2019-06-27 19:22:40.640854+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:40:641
2019-06-27 19:22:40.641015+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopBeforeTimers
2019-06-27 19:22:40.641383+0800 1129_PerformanceMonitor[66196:708905] dispatch_semaphore_signal:st=0,time:2019/06/27 07:22:40:641
2019-06-27 19:22:40.641608+0800 1129_PerformanceMonitor[66196:708905] runLoopObserverCallBack - kCFRunLoopBeforeSources
2019-06-27 19:22:40.646402+0800 1129_PerformanceMonitor[66196:709130] ---------卡顿信息
Incident Identifier: B05FBB6C-274B-47FE-97FA-CCC2E78539FA
CrashReporter Key:   TODO
Hardware Model:      x86_64
Process:         1129_Performance [66196]
Path:            /Users/cs/Library/Developer/CoreSimulator/Devices/2BAC277B-4BE9-4769-B3E0-12B8177803F9/data/Containers/Bundle/Application/95501A8D-58E3-4EEE-BB73-09BD40184682/1129_PerformanceMonitor.app/1129_PerformanceMonitor
Identifier:      com.jm.PerformanceMonitor
Version:         1.0 (1)
Code Type:       X86-64
Parent Process:  debugserver [66197]

Date/Time:       2019-06-27 11:22:36 +0000
OS Version:      Mac OS X 12.2 (18F132)
Report Version:  104

Exception Type:  SIGTRAP
Exception Codes: TRAP_TRACE at 0x10e2c3f74
Crashed Thread:  5

Thread 0:
0   libsystem_kernel.dylib              0x0000000111235f32 __semwait_signal + 10
1   libsystem_c.dylib                   0x0000000111019d24 usleep + 53
2   1129_PerformanceMonitor             0x000000010e2c4740 -[ViewController tableView:cellForRowAtIndexPath:] + 272
3   UIKitCore     

接下来我们对该结果分段解释说明

  • Resultado de la operación 2.1
2019-06-27 19:22:36.084560+0800 1129_PerformanceMonitor[66196:709130] st = 49,activity = 4,timeoutCount = 0,time:2019/06/27 07:22:36:084
2019-06-27 19:22:36.139575+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait:st=49,time:2019/06/27 07:22:36:139
2019-06-27 19:22:36.139918+0800 1129_PerformanceMonitor[66196:709130] st = 49,activity = 4,timeoutCount = 1,time:2019/06/27 07:22:36:140
2019-06-27 19:22:36.191970+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait:st=49,time:2019/06/27 07:22:36:192
2019-06-27 19:22:36.192274+0800 1129_PerformanceMonitor[66196:709130] st = 49,activity = 4,timeoutCount = 2,time:2019/06/27 07:22:36:192
2019-06-27 19:22:36.244942+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait:st=49,time:2019/06/27 07:22:36:245
2019-06-27 19:22:36.245238+0800 1129_PerformanceMonitor[66196:709130] st = 49,activity = 4,timeoutCount = 3,time:2019/06/27 07:22:36:245
2019-06-27 19:22:36.295878+0800 1129_PerformanceMonitor[66196:709130] dispatch_semaphore_wait:st=49,time:2019/06/27 07:22:36:296
2019-06-27 19:22:36.296166+0800 1129_PerformanceMonitor[66196:709130] st = 49,activity = 4,timeoutCount = 4,time:2019/06/27 07:22:36:296

Resultado de la operación 2.1 : Podemos encontrar que se ha excedido el intervalo de tiempo de cada impresión 50ms, es decir 信号量, el tiempo de espera alcanzado entrará en el if(st != 0)juicio, es decir runloop, el estado no ha cambiado durante mucho tiempo, y porque activity = 4, es decir, el runtoopestado actual es kCFRunLoopBeforeSources, Permanecer en este estado, significa que está haciendo muchas cosas. En este momento, se produce un fenómeno atascado y es necesario acumular el número de tiempos de espera timeoutCount.

  • Resultado de la operación 2.2
2019-06-27 19:22:40.646402+0800 1129_PerformanceMonitor[66196:709130] ---------卡顿信息
Incident Identifier: B05FBB6C-274B-47FE-97FA-CCC2E78539FA
CrashReporter Key:   TODO
Hardware Model:      x86_64
Process:         1129_Performance [66196]
Path:            /Users/cs/Library/Developer/CoreSimulator/Devices/2BAC277B-4BE9-4769-B3E0-12B8177803F9/data/Containers/Bundle/Application/95501A8D-58E3-4EEE-BB73-09BD40184682/1129_PerformanceMonitor.app/1129_PerformanceMonitor
Identifier:      com.jm.PerformanceMonitor
Version:         1.0 (1)
Code Type:       X86-64
Parent Process:  debugserver [66197]

Date/Time:       2019-06-27 11:22:36 +0000
OS Version:      Mac OS X 12.2 (18F132)
Report Version:  104

Exception Type:  SIGTRAP
Exception Codes: TRAP_TRACE at 0x10e2c3f74
Crashed Thread:  5

Thread 0:
0   libsystem_kernel.dylib              0x0000000111235f32 __semwait_signal + 10
1   libsystem_c.dylib                   0x0000000111019d24 usleep + 53
2   1129_PerformanceMonitor             0x000000010e2c4740 -[ViewController tableView:cellForRowAtIndexPath:] + 272
3   UIKitCore     

Resultado de ejecución 2.2: debido a que timeoutCountse ha acumulado el número de tiempos de espera 5y el runloopestado actual es kCFRunLoopBeforeSourceso kCFRunLoopAfterWaiting, se puede juzgar que se ha producido el atasco y se puede obtener la información de la pila actual.

  • Cómo bloquear la información de la pila
// 收集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);

Con la ayuda de una biblioteca de terceros CrashReport, la información de la pila actual se puede obtener a través de la biblioteca

Explicación gráfica

 

Dirección de enlace del proyecto-PerformanceMonitor



Autor: Luffy _Luck
link: https: //www.jianshu.com/p/d0aab0eb8ce4

Supongo que te gusta

Origin blog.csdn.net/wangletiancsdn/article/details/107758866
Recomendado
Clasificación