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

如果你去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 + 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线程发生未捕获异常,都可以先引导用户发送日志,再把进程崩溃掉。

 

转载于:https://www.cnblogs.com/alario/archive/2012/03/28/2421574.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/377613.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Discuz 升级X3问题汇总整理

最近一段时间公司的社区垃圾帖数量陡然上涨&#xff0c;以至于社区首页的推荐版块满满都是垃圾帖的身影&#xff0c;为了进一步解决垃圾帖问题我们整整花了1天时间删垃圾贴&#xff0c;清除不良用户&#xff0c;删的手都酸了&#xff0c;可见垃圾帖的数量之多&#xff01;可耻的…

【C++grammar】格式化输出与I/O流函数

目录1、格式化输出1. setw manipulator(“设置域宽”控制符)2. setprecision manipulator(“设置浮点精度”控制符)3. setfill manipulator(“设置填充字符”控制符)4. Formatting Output in File Operation(在文件操作中格式化输入/输出)5.小练习2、用于输入/输出流的函数1. g…

三、实战---爬取百度指定词条所对应的结果页面(一个简单的页面采集器)

在第一篇博文中也提及到User-Agent&#xff0c;表示请求载体的身份&#xff0c;也就是说明通过什么浏览器进行访问服务器的&#xff0c;这一点很重要。 ① UA检测 门户网站服务器会检测请求载体的身份。如果检测到载体的身份表示为某一款浏览器的请求&#xff0c;则说明这是一…

硕士毕业后去国外读法学博士_法学硕士的完整形式是什么?

硕士毕业后去国外读法学博士法学硕士&#xff1a;豆科大法师(拉丁)/法学硕士 (LLM: Legum Magister (Latin)/ Master of Law) LLM is an abbreviation of Legum Magister. It is in term of Latin which states the masters degree of Law. In the majority, LLM is generally …

android:layout_weight属性的简单使用

效果&#xff1a; style.xml <style name"etStyle2"><item name"android:layout_width">match_parent</item><item name"android:layout_height">wrap_content</item><item name"android:background"…

一、环境配置安装

一、Anaconda Ⅰ下载 最新版的anaconda可能会需要各种各样的问题&#xff0c;python3.6版本比较稳定&#xff0c;建议使用。 老铁们可以通过&#xff0c;Anaconda以前版本所自带Python版本&#xff0c;查看Anaconda所带的python版本 我用的是这个&#xff0c;Anaconda3-5.2.0…

二、PyTorch加载数据

一、常用的两个函数 dir()函数可以理解为打开某个包&#xff0c;help()可以理解为返回如何使用某个具体的方法 例如&#xff1a;若一个A钱包里面有a&#xff0c;b&#xff0c;c&#xff0c;d四个小包&#xff0c;则可通过dir(A)&#xff0c;打开该A钱包&#xff0c;返回a&…

leetcode 1005. K 次取反后最大化的数组和 思考分析

题目 给定一个整数数组 A&#xff0c;我们只能用以下方法修改该数组&#xff1a;我们选择某个索引 i 并将 A[i] 替换为 -A[i]&#xff0c;然后总共重复这个过程 K 次。&#xff08;我们可以多次选择同一个索引 i。&#xff09; 以这种方式修改数组后&#xff0c;返回数组可能…

三、TensorBoard

一、安装TensorBoard 管理员身份运行Anaconda Prompt&#xff0c;进入自己的环境环境 conda activate y_pytorch&#xff0c;pip install tensorboard 进行下载&#xff0c;也可以通过conda install tensorboard进行下载。其实通俗点&#xff0c;pip相当于菜市场&#xff0c;c…

详细讲解设计跳表的三个步骤(查找、插入、删除)

目录写在前面跳表概要查找步骤插入步骤删除步骤完整代码写在前面 关于跳表的一些知识可以参考这篇文章,最好是先看完这篇文章再看详细的思路->代码的复现步骤: Redis内部数据结构详解(6)——skiplist 关于跳表的插入、删除基本操作其实也就是链表的插入和删除&#xff0c;所…

php 类静态变量 和 常量消耗内存及时间对比

在对类执行100w次循环后&#xff0c; 常量最快&#xff0c;变量其次&#xff0c;静态变量消耗时间最高 其中&#xff1a; 常量消耗&#xff1a;101.1739毫秒 变量消耗&#xff1a;2039.7689毫秒 静态变量消耗&#xff1a;4084.8911毫秒 测试代码&#xff1a; class Timer_profi…

一个机器周期 计算机_计算机科学组织| 机器周期

一个机器周期 计算机机器周期 (Machine Cycle) The cycle during which a machine language instruction is executed by the processor of the computer system is known as the machine cycle. If a program contains 10 machine language instruction, 10 separate machine …

四、Transforms

transform是torchvision下的一个.py文件&#xff0c;这个python文件中定义了很多的类和方法&#xff0c;主要实现对图片进行一些变换操作 一、Transforms讲解 from torchvision import transforms#按着Ctrl&#xff0c;点击transforms进入到__init__.py文件中 from .transfo…

五、torchvision

一、下载CIFAR-10数据集 CIFAR-10数据集官网 通过阅读官网给的解释可以大概了解到&#xff0c;一共6w张图片&#xff0c;每张图片大小为3232&#xff0c;5w张训练图像&#xff0c;1w张测试图像&#xff0c;一共由十大类图像。 CIFAR10官网使用文档 torchvision.datasets.CIF…

转 设计师也需要了解的一些前端知识

一、常见视觉效果是如何实现的 一些事 关于文字效果 互联网的一些事 文字自身属性相关的效果css中都是有相对应的样式的&#xff0c;如字号、行高、加粗、倾斜、下划线等&#xff0c;但是一些特殊的效果&#xff0c;主要表现为ps中图层样式中的效果&#xff0c;css是无能为力的…

六、DataLoader

一、DataLoader参数解析 DataLoader官网使用手册 参数描述dataset说明数据集所在的位置、数据总数等batch_size每次取多少张图片shuffleTrue乱序、False顺序(默认)samplerbatch_samplernum_workers多进程&#xff0c;默认为0采用主进程加载数据collate_fnpin_memorydrop_las…

七、torch.nn

一、神经网络模块 进入到PyTorch的torch.nnAPI学习页面 PyTorch提供了很多的神经网络方面的模块&#xff0c;NN就是Neural Networks的简称 二、Containers torch.nn下的Containers 一共有六个模块&#xff0c;最常用的就是Module模块&#xff0c;看解释可以知道&#xff0c…

Java多线程初学者指南(8):从线程返回数据的两种方法

本文介绍学习Java多线程中需要学习的从线程返回数据的两种方法。从线程中返回数据和向线程传递数据类似。也可以通过类成员以及回调函数来返回数据。原文链接 从线程中返回数据和向线程传递数据类似。也可以通过类成员以及回调函数来返回数据。但类成员在返回数据和传递数据时有…

【C++进阶】 遵循TDD原则,实现平面向量类(Vec2D)

目录1、明确要实现的类的方法以及成员函数2、假设已经编写Vec2D&#xff0c;根据要求&#xff0c;写出测试代码3、编写平面向量类Vec2D,并进行测试4、完整代码5、最终结果1、明确要实现的类的方法以及成员函数 考虑到效率问题&#xff0c;我们一般将函数的参数设置为引用类型。…

md5模式 签名_MD的完整形式是什么?

md5模式 签名医师&#xff1a;医学博士/常务董事 (MD: Doctor of Medicine / Managing Director) 1)医学博士&#xff1a;医学博士 (1) MD: Doctor of Medicine) MD is an abbreviation of a Doctor of Medicine degree. In the field of Medicine, it is the main academic de…