之前写的几篇文章iOS调试技巧, LLDB, LLVM, 解析dSYM文件 都是可以有效调试错误, 这篇文章主要是想记录一下我探索AvoidCrash的结果, 做个小节.
题外话: iOS版本管理
在Swift中, 编译器不再支持预处理指令。作为替代,它使用编译时的属性和build配置
在Xcode
中Build Setting
界面, 搜索flags
, 最下面就是.
我们可以通过在不同的环境下设置不同的Tag
, 来控制版本.
可以使用#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")
如何避免运行中的App因为Crash崩溃呢
先讲讲Object-C的方案
- 利用
rutime
中的方法交换, 比如为了拦截selector
不存在的错误, 将系统的方法methodSignatureForSelector
和forwardInvocation
, 换成我们自己的方法.
- 利用
- 利用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===");
}
}
- 分析
exception
, 和函数调用栈, 来找到出错的原因和具体出错的代码.
- 分析
那怎么分析呢?
-
NSException
包含reason
,name
, 可以获取到出错的原因. - 通过
[NSThread callStackSymbols]
可以获取到当前线程的调用栈信息.
- 在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