iOS面试基础知识点解决方案

iOS Crash原理及日志收集

2020-04-23  本文已影响0人  哦小小树

0x01 常见的Crash类型

KVO
NSNotification
数组越界
内存爆炸
野指针
后台任务超时
主线程卡顿超出阈值
死锁
CPU消耗过大


0x02 系统提供的异常捕获

NSUncaughtExceptionHandler

示例代码


- (void)crashMonitor {
    NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);   // 添加异常监听句柄
}

void UncaughtExceptionHandler(NSException *exception) {
    NSString *name = [exception name];
    NSString *reason = [exception reason];
    NSArray *stackTrace = [exception callStackSymbols];
    NSString *crashInfo = [NSString stringWithFormat:@"\n%@\n%@\n%@\n",name,reason, stackTrace];
    
    [[CrashManager shareInstance] saveCrashToFile:crashInfo];  // 存储crash文件
}

Signal捕获

操作逻辑

  1. iOS系统出现crash的时候会发出signal
  2. 注册signal及回调配置【句柄】
// 注册句柄, SIG_Flag为要监听的signal, handleCallBack为回调函数
signal(SIG_Flag, handleCallBack)

示例代码

    // signal.h中包含所有指令
    signal(SIGABRT, handleSignal);      // 调用abort
    signal(SIGILL, handleSignal);       // 非法指令
    signal(SIGSEGV, handleSignal);      // 无效内存引用
    signal(SIGPIPE, handleSignal);      // 端口消息发送失败

如果需要测试,需要再接收信号前通过lldb配置:
pro hand -p true -s false SIGABRT

void handleSignal(int signal) {
    NSLog(@"中断信信号:%@",signal);
    // 打印函数调用栈
}

这内部处理信号的逻辑涉及到各种内核,线程状态操作。后续研究再来完善。


0x03 日志记录

// 日志目录路径
- (NSString *)logDirectory {
    NSString *directory =  [NSHomeDirectory() stringByAppendingPathComponent:@"log"];
    
    if (![[NSFileManager defaultManager] fileExistsAtPath:directory]) {
        [[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:nil];
    }
    return directory;
}
// 日志文件信息
- (NSString *)crashFilePath {
    return [[self logDirectory] stringByAppendingPathComponent:@"crash.log"];
}
// 保存日志信息
- (void)saveCrashToFile:(NSString *)crashInfo {
    NSFileManager *manager = [NSFileManager defaultManager];
    if (![manager fileExistsAtPath:[self crashFilePath]]) {
        [manager createFileAtPath:[self crashFilePath] contents:nil attributes:nil];
    }
    NSFileHandle *handler = [NSFileHandle fileHandleForWritingAtPath:[self crashFilePath]];
    [handler seekToEndOfFile];  // 拼接形式操作
    [handler writeData:[crashInfo dataUsingEncoding:NSUTF8StringEncoding]];
}
- (NSArray *)sortedCrashFiles {
    // 目录中所有日志文件
    NSFileManager *mana = [NSFileManager defaultManager];
    if (![mana fileExistsAtPath:[self logDirectory]]) return nil;
    // 返回文件名列表
    NSArray *filesArr = [mana contentsOfDirectoryAtPath:[self logDirectory] error:nil];
    // 如果需要对文件做些筛选可以在此处处理
    
    NSArray *sortArr = [filesArr sortedArrayUsingComparator:^NSComparisonResult(NSString *  _Nonnull obj1, NSString *  _Nonnull obj2) {
        // 配置全路径,找到文件,以文件创建时间进行排序
//      NSString *filePath1 = [[self logDirectory] stringByAppendingPathComponent:obj1];
//      NSString *filePath2 = [[self logDirectory] stringByAppendingPathComponent:obj2];
//
//      NSDictionary *file1Dcit = [mana attributesOfItemAtPath:filePath1 error:nil];
//      NSDictionary *file2Dict = [mana attributesOfItemAtPath:filePath2 error:nil];
//
//      NSDate *file1Date = [file1Dcit objectForKey:NSFileCreationDate];
//      NSDate *file2Date = [file2Dict objectForKey:NSFileCreationDate];
//      return [file1Date compare:file2Date];
        return [obj1 compare:obj2];     // 直接按照文件名进行排序
    }];
    
    NSMutableArray *tmpArr = @[].mutableCopy;
    for (NSString *fileName in sortArr) {  // 拼接为全路径
        [tmpArr addObject:[[self logDirectory] stringByAppendingPathComponent:fileName]];
    }
    return tmpArr;
    
}

使用SSZipArchive进行资源压缩

- (NSString *)zipFileForAllCrash {
    // 压缩所有的crash文件, 返回压缩好的文件路径
    NSArray *sortArray = [self sortedCrashFiles];
    
    if (sortArray.count <= 0) return nil;
    // 创建一个压缩文件路径
    NSString *crashZipPath = [[self logDirectory] stringByAppendingPathComponent:[[self timeString] stringByAppendingString:@".zip"]];
    
    // 根据路径压缩所有文件信息
    BOOL ret = [SSZipArchive createZipFileAtPath:crashZipPath withFilesAtPaths:sortArray];
    
    if (!ret) { // 压缩失败
        crashZipPath = nil;
        NSLog(@"创建压缩文件失败");
    }
    return crashZipPath;
}

获取到压缩后的路径可以执行上传操作,当提交成功后就可以删除本地日志信息

- (void)clearAllCacheLogFile {
    NSFileManager *mana = [NSFileManager defaultManager];
    // 删除所有文件
    [mana removeItemAtPath:[self logDirectory] error:nil];
}

示例代码
https://github.com/linfengwenyou/CrashCollectTest


0x03 主流的崩溃日志收集框架


参考资料:
https://blog.csdn.net/weixin_36139431/article/details/95358484
https://www.it610.com/article/1191455498289913856.htm

上一篇下一篇

猜你喜欢

热点阅读