热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

经营你的iOS应用日志(二):异常日志

原文地址:http:www.cnblogs.comalario如果你去4S店修车,给小工说你的车哪天怎么样怎么样了,小工有可能会立即搬出

原文地址:http://www.cnblogs.com/alario/

如果你去4S店修车,给小工说你的车哪天怎么样怎么样了,小工有可能会立即搬出一台电脑,插上行车电脑把日志打出来,然后告诉你你的车发生过什么故障。汽车尚且如此,何况移动互联网应用呢。

本文第一篇:经营你的iOS应用日志(一):开始编写日志组件

 

言归正传。开发iOS应用,解决Crash问题始终是一个难题。Crash分为两种,一种是由EXC_BAD_ACCESS引起的,原因是访问了不属于本进程的内存地址,有可能是访问已被释放的内存;另一种是未被捕获的Objective-C异常(NSException),导致程序向自身发送了SIGABRT信号而崩溃。其实对于未捕获的Objective-C异常,我们是有办法将它记录下来的,如果日志记录得当,能够解决绝大部分崩溃的问题。这里对于UI线程与后台线程分别说明。

先看UI线程。iOS SDK提供了NSSetUncaughtExceptionHandler函数,用法如:


NSSetUncaughtExceptionHandler( handleRootException );

这样在UI线程发生未捕获异常后,进程崩溃之前,handleRootException会被执行。这个函数实现如下


static void handleRootException( NSException* exception )
{NSString* name = [ exception name ];NSString* reason = [ exception reason ];NSArray* symbols = [ exception callStackSymbols ]; // 异常发生时的调用栈
NSMutableString* strSymbols = [ [ NSMutableString alloc ] init ]; // 将调用栈拼成输出日志的字符串
for ( NSString* item in symbols ){[ strSymbols appendString: item ];[ strSymbols appendString: @"\r\n" ];}// 写日志,级别为ERROR
writeCinLog( __FUNCTION__, CinLogLevelError, @"[ Uncaught Exception ]\r\nName: %@, Reason: %@\r\n[ Fe Symbols Start ]\r\n%@[ Fe Symbols End ]", name, reason, strSymbols );[ strSymbols release ];// 这儿必须Hold住当前线程,等待日志线程将日志成功输出,当前线程再继续运行
blockingFlushLogs( __FUNCTION__ );// 写一个文件,记录此时此刻发生了异常。这个挺有用的哦
NSDictionary* dict = [ NSDictionary dictionaryWithObjectsAndKeys:currentCinLogFileName(), @"LogFile", // 当前日志文件名称
currentCinLogFileFullPath(), @"LogFileFullPath", // 当前日志文件全路径
[ NSDate date ], @"TimeStamp", // 异常发生的时刻
nil ];NSString* path = [ NSString stringWithFormat: @"%@/Documents/", NSHomeDirectory() ];NSString* lastExceptionLog = [ NSString stringWithFormat: @"%@LastExceptionLog.txt", path ];[ dict writeToFile: lastExceptionLog atomically: YES ];}

而我们的日志组件必须实现blockingFlushLogs函数,确保进程在日志完全写入文件后再退出。这个实现应该很简单吧。

当应用下次启动时,我们可以检查,如果有LastExceptionLog.txt,则弹窗引导测试人员将日志发过来。如果iPhone上面配置了EMail帐户,可以很简单的调用MFMailComposeViewController将日志文件作为附件发送,当然也可以想其它办法。

记得正式发布的版本要将它条件编译去掉哦。

其中文件中的最后一条ERROR即为导致崩溃的异常,而从ERROR之前的日志可以看出当前程序的运行情况。ERROR如下:


<- 03-20 17:21:43 ERROR -> [UI] -[CinUIRunLoopActionManager(Protected) handleRootException:]
[ Uncaught Exception ]
Name: NSDestinationInvalidException, Reason: *** -[CinThreadRunLoopActionManager performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform
[ Fe Symbols Start ]
0 CoreFoundation 0x340c88d7 __exceptionPreprocess &#43; 186
1 libobjc.A.dylib 0x343181e5 objc_exception_throw &#43; 32
2 CoreFoundation 0x340c87b9 &#43;[NSException raise:format:] &#43; 0
3 CoreFoundation 0x340c87db &#43;[NSException raise:format:] &#43; 34
4 Foundation 0x35a12493 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:] &#43; 998
5 Foundation 0x35a3afb5 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:] &#43; 108
6 MyiOSapplication 0x0022b7e9 -[CinThreadRunLoopActionManager(Protected) performAction:] &#43; 144
13 UIKit 0x374b36b5 -[UIViewController _setViewAppearState:isAnimating:] &#43; 144
14 UIKit 0x374b38c1 -[UINavigationController viewWillAppear:] &#43; 288
15 UIKit 0x374b36b5 -[UIViewController _setViewAppearState:isAnimating:] &#43; 144
16 UIKit 0x3750e61b -[UIViewController beginAppearanceTransition:animated:] &#43; 190
17 UIKit 0x3750b415 -[UITabBarController transitionFromViewController:toViewController:transition:shouldSetSelected:] &#43; 184
18 UIKit 0x3750b357 -[UITabBarController transitionFromViewController:toViewController:] &#43; 30
19 UIKit 0x3750ac91 -[UITabBarController _setSelectedViewController:] &#43; 300
20 UIKit 0x3750a9c5 -[UITabBarController setSelectedIndex:] &#43; 240
21 MyiOSapplication 0x0007ef1d &#43;[Utility ResetCurrentTabIndex] &#43; 172
22 MyiOSapplication 0x001a87bd -[UIViewController(statusBar) dismissModalViewControllerAnimatedEx:] &#43; 416
23 MyiOSapplication 0x001793fb -[ImageProcessingViewController save:] &#43; 690
24 CoreFoundation 0x34022435 -[NSObject performSelector:withObject:withObject:] &#43; 52
25 UIKit 0x3748c9eb -[UIApplication sendAction:to:from:forEvent:] &#43; 62
26 UIKit 0x3748c9a7 -[UIApplication sendAction:toTarget:fromSender:forEvent:] &#43; 30
27 UIKit 0x3748c985 -[UIControl sendAction:to:forEvent:] &#43; 44
28 UIKit 0x3748c6f5 -[UIControl(Internal) _sendActionsForEvents:withEvent:] &#43; 492
29 UIKit 0x3748d02d -[UIControl touchesEnded:withEvent:] &#43; 476
30 UIKit 0x3748b50f -[UIWindow _sendTouchesForEvent:] &#43; 318
31 UIKit 0x3748af01 -[UIWindow sendEvent:] &#43; 380
32 UIKit 0x374714ed -[UIApplication sendEvent:] &#43; 356
33 UIKit 0x37470d2d _UIApplicationHandleEvent &#43; 5808
34 GraphicsServices 0x308a3df3 PurpleEventCallback &#43; 882
35 CoreFoundation 0x3409c553 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ &#43; 38
36 CoreFoundation 0x3409c4f5 __CFRunLoopDoSource1 &#43; 140
37 CoreFoundation 0x3409b343 __CFRunLoopRun &#43; 1370
38 CoreFoundation 0x3401e4dd CFRunLoopRunSpecific &#43; 300
39 CoreFoundation 0x3401e3a5 CFRunLoopRunInMode &#43; 104
40 GraphicsServices 0x308a2fcd GSEventRunModal &#43; 156
41 UIKit 0x3749f743 UIApplicationMain &#43; 1090
42 MyiOSapplication 0x000d4ccb main &#43; 174
43 MyiOSapplication 0x000039c8 start &#43; 40
[ Fe Symbols End ]

可以看到&#xff0c;即使我们没有编译时生成的符号文件&#xff0c;也能够打印出调用栈上的每个函数的名称&#xff0c;只是没有文件名和行号。

那么&#xff0c;除了UI线程之外&#xff0c;自己创建的后台线程呢&#xff1f;运行NSRunLoop的后台线程的线程函数应该如下&#xff1a;


- ( void ) threadProc: ( NSString* )threadName
{NSThread* current &#61; [ NSThread currentThread ];[ current setName: threadName ];NSAutoreleasePool *pool &#61; [ [ NSAutoreleasePool alloc ] init ];// 一个没有实际作用的NSTimer&#xff0c;确保NSRunLoop不退出。不知道有没有更好的办法啊
_dummyTimer &#61; [ [ NSTimer timerWithTimeInterval: 10.0target: selfselector: &#64;selector( dummyTimerProc: )userInfo: nilrepeats: YES ] retain ];NSRunLoop *r &#61; [ NSRunLoop currentRunLoop ];[ r addTimer: _dummyTimer forMode: NSDefaultRunLoopMode ];&#64;try {// 启动后台线程的NSRunLoop
[ r run ];}&#64;catch ( NSException *exception ) {[ self handleRootException: exception ];// 一旦在线程根上捕捉到未知异常&#xff0c;记录异常后本线程退出
}&#64;finally {[ _dummyTimer invalidate ];[ _dummyTimer release ];[ pool release ];}
}

后台线程的handleRootException与UI线程基本一致。不过为了测试人员更加方便&#xff0c;其实只要不是UI线程发生未捕获异常&#xff0c;都可以先引导用户发送日志&#xff0c;再把进程崩溃掉。




推荐阅读
author-avatar
缺氧
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有