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

本文探讨了iOS应用中Crash问题的两种类型及解决方法,重点介绍了如何通过NSSetUncaughtExceptionHandler函数记录未捕获的Objective-C异常,并提供了解决方案以确保应用稳定运行。此外,文章还讨论了UI线程与后台线程处理Crash问题的方法,包括如何通过日志组件实现错误记录和异常报告。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

那么,除了UI线程之外,自己创建的后台线程呢?运行NSRunLoop的后台线程的线程函数应该如下:

复制代码
- ( void ) threadProc: ( NSString* )threadName
{
NSThread* current = [ NSThread currentThread ];
[ current setName: threadName ];
NSAutoreleasePool *pool = [ [ NSAutoreleasePool alloc ] init ];

// 一个没有实际作用的NSTimer,确保NSRunLoop不退出。不知道有没有更好的办法啊
_dummyTimer = [ [ NSTimer timerWithTimeInterval: 10.0
target: self
selector: @selector( dummyTimerProc: )
userInfo: nil
repeats: YES ] retain ];

NSRunLoop *r = [ NSRunLoop currentRunLoop ];
[ r addTimer: _dummyTimer forMode: NSDefaultRunLoopMode ];
@try {
// 启动后台线程的NSRunLoop
[ r run ];
}
@catch ( NSException *exception ) {
[ self handleRootException: exception ];
// 一旦在线程根上捕捉到未知异常,记录异常后本线程退出
}
@finally {
[ _dummyTimer invalidate ];
[ _dummyTimer release ];
[ pool release ];
}
}
复制代码

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

转自:http://www.cnblogs.com/alario/archive/2012/03/28/2421574.html

内容概要:本文系统介绍了基于C#(VS2022+.NET Core)与HALCON 24.11的工业视觉测量拟合技术,涵盖边缘提取、几何拟合、精度优化及工业部署全流程。文中详细解析了亚像素边缘提取、Tukey抗噪算法、SVD平面拟合等核心技术,并提供了汽车零件孔径测量、PCB焊点共面性检测等典型应用场景的完整代码示例。通过GPU加速、EtherCAT同步等优化策略,实现了±0.01mm级测量精度,满足ISO 1101标准。此外,文章还探讨了深度学习、量子启发式算法等前沿技术的应用前景。 适合人群:具备一定编程基础,尤其是熟悉C#和HALCON的工程师或研究人员,以及从事工业视觉测量与自动化检测领域的技术人员。 使用场景及目标:①学习如何使用C#和HALCON实现高精度工业视觉测量系统的开发;②掌握边缘提取、抗差拟合、3D点云处理等核心技术的具体实现方法;③了解工业部署中的关键技术,如GPU加速、EtherCAT同步控制、实时数据看板等;④探索基于深度学习和量子计算的前沿技术在工业视觉中的应用。 其他说明:本文不仅提供了详细的理论分析和技术实现,还附有完整的代码示例和实验数据,帮助读者更好地理解和实践。同时,文中提到的硬件选型、校准方法、精度验证等内容,为实际项目实施提供了重要参考。文章最后还给出了未来的技术演进方向和开发者行动建议,如量子-经典混合计算、自监督学习等,以及参与HALCON官方认证和开源社区的建议。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值