-
前言
曾經我以為我的程序可以一帆風順,知道我們遇到了你-crash,那是一段令人嘔吐的回憶。
-
背景
在實際項目開發中,我們會遇到很多不同的問題,有寫嘛隨著編程經驗豐富,隨手就可以解決了,但是如果你的程序以及發布到商店了,并且已經通過審核,某天某個客戶反饋:“你這個是個什么XX%%XX%%,辣雞,閃退”;又或者把程序給測試人員了,那邊反應閃退了,but 沒有現場,不知道原因,排查起來那也是一件很憂傷的事情。
-
結論
綜上所述,我們還是有必要寫一個抓取奔潰信息的東東的:
第一:能讓你的程序越來越強壯
第二:能在一定程度上讓你快速修復BUG
第三:能讓你知道你踏了哪些坑
第四:還能和測試妹子多溝通不是(你把手機拿過來哦,我來看看log信息,然后測試妹子就來了……咳~正經一點,我們是討論技術的)
好在Apple已經提供了方法給我們了,不然又得讓吃螃蟹的人死多少腦細胞。
<br />
個人拙見
這個是系統定義的一個方法,讓我們傳入一個方法地址(不能亂寫的),當程序奔潰了就會觸發這個方法,不僅僅可以用來獲取log信息,還可以保存一下數據。
typedef void NSUncaughtExceptionHandler(NSException *exception);
FOUNDATION_EXPORT NSUncaughtExceptionHandler * _Nullable NSGetUncaughtExceptionHandler(void);
FOUNDATION_EXPORT void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable);
<br />
為了能不在AppDelegate中寫那么多冗余的代碼,于是我把他稍微的封裝了一下
1、先導入頭文件
#import "PQCatchErrorLog.h"
<br />
2、“注冊”抓取Log
這里之所以用雙引號因為我也不知道怎么去描述,
但是和通知很像,當發送奔潰時才會通知,所以這里就相當于注冊通知,大家仁者見仁智者見智。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[PQCatchErrorLog catchLog];
[PQCatchErrorLog printErrorLog];
return YES;
}
3、...
然后?然后就沒了……
此外還提供了
/**
得到LOG信息
@return log信息 - NSString
*/
+ (NSString *)logInfo;
/**
得到LOG信息,以便于上傳
@return log信息 - Data
*/
+ (NSData *)logData;
/**
刪除error信息
@return 返回是否刪除成功
*/
+ (BOOL)delErrorLogFile;
對應的實現代碼
+ (void)catchLog{
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
}
- (instancetype)init
{
self = [super init];
if (self) {
//捕捉崩潰信息
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
}
return self;
}
+ (void)printErrorLog{
NSLog(@"path - %@ \nerrorLog - %@",LOGFilePath,[PQCatchErrorLog logInfo]);
}
+ (NSString *)logInfo{
return [NSString stringWithContentsOfFile:LOGFilePath encoding:NSUTF8StringEncoding error:nil];
}
+ (NSData *)logData{
return [[PQCatchErrorLog logInfo] dataUsingEncoding:NSUTF8StringEncoding];
}
+ (BOOL)delErrorLogFile{
NSError * error;
[[NSFileManager defaultManager] removeItemAtPath:LOGFilePath error:&error];
if (!error) {
return YES;
}
NSLog(@"\n刪除失敗 - %@",error);
return NO;
}
<br />
并且為了記錄時間,我還寫了一個類別方法,一起寫在了類中
.h
// ---------------- 把時間轉化為字符串 -----------------
@interface NSDate (PQCatchErrorLog)
/**
把當前時間轉化字符串
@return 當前時間字符串
*/
+ (NSString *)currentDateForDateSeconds;
@end
.m
/**
把當前時間轉化字符串
@return 當前時間字符串
*/
+ (NSString *)currentDateForDateSeconds{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSString *destDateString1 = [dateFormatter stringFromDate:[NSDate date]];
NSString *destDateString = [destDateString1 substringFromIndex:2];
return destDateString;
}
<br />
還有一個地址拼接的
.h
// ---------------- 文件地址拼接 -----------------
@interface NSString (PQCatchErrorLog)
/**
為字符串添加地址
@return 地址
*/
- (NSString *)byAppendToCacheDocument;
@end
.m
/**
為字符串添加地址
@return 地址
*/
- (NSString *)byAppendToCacheDocument{
NSString * path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
return [path stringByAppendingPathComponent:self];
}
<br />
<br /><br />
最后在介紹一下:如何保存log信息
//抓取奔潰日志
void UncaughtExceptionHandler(NSException *exception) {
NSArray *arr = [exception callStackSymbols];//得到當前調用棧信息
NSString *reason = [exception reason];//非常重要,就是崩潰的原因
NSString *name = [exception name];//異常類型
NSMutableString * log = [NSMutableString stringWithFormat:@"callStackSymbols - 當前棧信息:\n"];
for (NSString * str in arr) {
[log appendFormat:@"%@\n",str];
}
[log appendFormat:@"\nreason - 崩潰原因:\n %@",reason];
[log appendFormat:@"\nname - 異常類型:\n %@",name];
[log insertString:[NSString stringWithFormat:@"*************** %@ *******************\n",[NSDate currentDateForDateSeconds]] atIndex:0];
//創建一個文件 如果是第一次就直接寫入,然后返回
if (![[NSFileManager defaultManager]fileExistsAtPath:LOGFilePath]) {
[[NSFileManager defaultManager]createFileAtPath:LOGFilePath contents:nil attributes:nil];
[log insertString:[NSString stringWithFormat:@"\n*************** 奔潰日志 *******************\n"] atIndex:0];
[log writeToFile:LOGFilePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
return;
}
//創建一個fileHandler
NSFileHandle * fileHandler = [NSFileHandle fileHandleForWritingAtPath:LOGFilePath];
//跳到文件末尾
[fileHandler seekToEndOfFile];
//寫入文件
[fileHandler writeData:[log dataUsingEncoding:NSUTF8StringEncoding]];
//關閉file Handler
[fileHandler closeFile];
}
最后故意在ViewController中寫了一個數組越界的BUG
大家自行感受一下:
*************** 奔潰日志 *******************
*************** 16-11-28 15:32:47 *******************
callStackSymbols - 當前棧信息:
0 CoreFoundation 0x000000010c91634b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x000000010c37721e objc_exception_throw + 48
2 CoreFoundation 0x000000010c96ebdf -[__NSSingleObjectArrayI objectAtIndex:] + 111
3 CatchErrorLog抓取奔潰信息 0x000000010bda22a3 -[ViewController viewDidLoad] + 163
4 UIKit 0x000000010cedac99 -[UIViewController loadViewIfRequired] + 1258
5 UIKit 0x000000010cedb0cc -[UIViewController view] + 27
6 UIKit 0x000000010cda4c51 -[UIWindow addRootViewControllerViewIfPossible] + 71
7 UIKit 0x000000010cda53a2 -[UIWindow _setHidden:forced:] + 293
8 UIKit 0x000000010cdb8cb5 -[UIWindow makeKeyAndVisible] + 42
9 UIKit 0x000000010cd31c89 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4818
10 UIKit 0x000000010cd37de9 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1731
11 UIKit 0x000000010cd34f69 -[UIApplication workspaceDidEndTransaction:] + 188
12 FrontBoardServices 0x000000010fe87723 __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
13 FrontBoardServices 0x000000010fe8759c -[FBSSerialQueue _performNext] + 189
14 FrontBoardServices 0x000000010fe87925 -[FBSSerialQueue _performNextFromRunLoopSource] + 45
15 CoreFoundation 0x000000010c8bb311 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
16 CoreFoundation 0x000000010c8a059c __CFRunLoopDoSources0 + 556
17 CoreFoundation 0x000000010c89fa86 __CFRunLoopRun + 918
18 CoreFoundation 0x000000010c89f494 CFRunLoopRunSpecific + 420
19 UIKit 0x000000010cd337e6 -[UIApplication _run] + 434
20 UIKit 0x000000010cd39964 UIApplicationMain + 159
21 CatchErrorLog抓取奔潰信息 0x000000010bda280f main + 111
22 libdyld.dylib 0x000000010f6f668d start + 1
reason - 崩潰原因:
*** -[__NSSingleObjectArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 0]
name - 異常類型:
NSRangeException
在第3行:標出來了在哪個方法中
在第2行:標出來了是用數組取值
在崩潰原因中標出來了: 數組越界
最后一行是:異常類型
<br />
最后的最后
DEMO地址 如果有用,煩請star