美文网首页
Swift 关于Crash的一些看法

Swift 关于Crash的一些看法

作者: Lin__Chuan | 来源:发表于2018-09-21 22:38 被阅读226次

之前写的几篇文章iOS调试技巧, LLDB, LLVM, 解析dSYM文件 都是可以有效调试错误, 这篇文章主要是想记录一下我探索AvoidCrash的结果, 做个小节.

题外话: iOS版本管理

在Swift中, 编译器不再支持预处理指令。作为替代,它使用编译时的属性和build配置
XcodeBuild Setting界面, 搜索flags, 最下面就是.
我们可以通过在不同的环境下设置不同的Tag, 来控制版本.

flags

可以使用#if判定build的参数动态编译

#if DEBUG         // 调试版本
let appURL = "www.baidu.com"
#elseif RELEASE   // 发布版本
let appURL = "www.jack.com"
#else             // 其他版本
let appURL = "offical"
#endif

自定义Log

public func LCLog(_ msg: @autoclosure () -> String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) {
    
    #if DEBUG
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        let time = formatter.string(from: Date())
        
        let msgString = "\(time) \(String(describing: function))  \(String(describing: line)+"-"+msg())\n\n"
        print(msgString)
    #endif
}

还有一种方式实现版本管理就是通过切换不同的Target. 这里在参考中有详解.

题外话: Bugly的使用

腾讯Bugly官网登录账号, 注册应用. 获取到AppKey.
文档中心, 查看具体使用

  • 直接利用Cocopods, 在Podfile文件中, 写入" pod 'Bugly' "
  • 在代码中写入下面的代码, 若App运行出错, 会自动提交错误, 可在Bugly官网后台查看
Bugly.start(withAppId: "xxxx")
Bugly错误显示.png

如何避免运行中的App因为Crash崩溃呢

先讲讲Object-C的方案

    1. 利用rutime中的方法交换, 比如为了拦截selector不存在的错误, 将系统的方法methodSignatureForSelectorforwardInvocation, 换成我们自己的方法.
    1. 利用try-catch-finally来捕获运行时抛出的错误
-(NSMethodSignature *)lc_methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *ms = [self lc_methodSignatureForSelector:aSelector];
    if ([self respondsToSelector:aSelector] || ms) {
        return ms;
    }
    else {
        return [LCSafeProxy lc_methodSignatureForSelector:@selector(safe_crashLog)];
    }
}

-(void)lc_forwardInvocation:(NSInvocation *)anInvocation {
    @try {
        [self lc_forwardInvocation:anInvocation];
    } @catch (NSException *exception) {
        NSLog(exception);
    } @finally {
        NSLog(@"===finally===");
    }
}
    1. 分析exception, 和函数调用栈, 来找到出错的原因和具体出错的代码.

那怎么分析呢?

  • NSException 包含 reason, name, 可以获取到出错的原因.
  • 通过 [NSThread callStackSymbols] 可以获取到当前线程的调用栈信息.
    image.png
  • 在LLDB环境下, 通过寻址指令, 可以直接找到出错的代码具体位置.
(lldb) image lookup --address 0x0000000104d8267d
      Address: TestAvoidCrash[0x000000010000467d] (TestAvoidCrash.__TEXT.__text + 11485)
      Summary: TestAvoidCrash`-[ViewController testString] + 61 at ViewController.m:52
  • 如果不是在本地调试, 无法使用LLDB环境怎么办呢?, 这时候就需要通过分析
    .dSYM文件(符号表信息), 来定位到具体到的出错地址. 这里可以使用Bugly, 来实时监控出错的代码. 也可以参考.dSYM文件分析.

小节:

  • 正式因为try-catch 机制, 将本应抛出的 exception 捕获了, 程序才不会崩溃.
  • 通过对当前线程的调用栈信息的分析, 可以找出出错代码的位置.

在Swift中怎么拦截crash, 避免程序崩溃呢?
目前除了调用 try-catch 方法, 并无其他比较好的解决方案, 但是这也是只能拦截 Foundation 框架里面的 NSArray , NSString 这种类型的. 并不能处理Array, String这种结构体.

LCTryCatch.h
@interface LCTryCatch : NSObject
+(void)try:(void(^)(void))try
     catch:(void(^)(NSException *e))catch
   finally:(void(^)(void))finally;
@end

LCTryCatch.m
@implementation LCTryCatch

+(void)try:(void (^)(void))try
     catch:(void (^)(NSException *))catch
   finally:(void (^)(void))finally
{
    @try {
        try? try() : nil;
        
    } @catch (NSException *exception) {
        catch? catch(exception) : nil;
        
    } @finally {
        finally? finally() : nil;
    }
}
@end
LCTryCatch.try({             
      NSArray().object(at: 1)
            
}, catch: { (exception) -> Void in        
      LCLog("\(exception)")
}) { () -> Void in
      LCLog("finally")          
}

既然写到这里, 推荐一个大佬写的 Swift 框架GodEye, 里面有一个CrashEye, 就是通过NSSetUncaughtExceptionHandler 和 signal(SIGABRT, SignalHandler), 来处理异常, 但是无法避免崩溃, 具体我在iOS崩溃日志里有提到.

参考
最详细 Xcode的Targets管理项目的公开版本、测试版本、预发布版本等等
OC版LSSafeProtector
Swift版CrashEye

相关文章

网友评论

      本文标题:Swift 关于Crash的一些看法

      本文链接:https://www.haomeiwen.com/subject/hlpkwftx.html