场景 4: 吃棒棒糖时闪退!
用户邮件说, “当rage master吃棒棒糖时应用就闪退…” 另一用户说, “我让rage master 吃棒棒糖,没几次应用就闪退了!” 崩溃日志如下:
Incident Identifier: 081E58F5- 95A8- 404D- 947B- 5E104B6BC1B1
CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31
Hardware Model: iPhone4,1
OS Version: iPhone OS 6.0 ( 10A403)
Kernel Version: Darwin Kernel Version 13.0.0: Sun Aug 19 00: 28 : 05 PDT 2012 ; root: xnu- 2107.2.33~4 / RELEASE_ARM_S5L8940X
Date: 2012 - 11 - 03 13 : 39 : 59 - 0400
Time since snapshot: 4353 ms
Free pages: 968
Active pages: 7778
Inactive pages: 4005
Throttled pages: 92319
Purgeable pages: 0
Wired pages: 23347
Largest process: Rage Masters
Processes
Name & lt;UUID& gt; rpages recent_max [ reason] ( state)
lsd & lt;6a9f5b5f36b23fc78f87b6d8f1f49a9d& gt; 331 331 [ vm] ( daemon) ( idle)
afcd & lt;b0aff2e7952e34a9882fec81a8dcdbb2& gt; 141 141 [ vm] ( daemon) ( idle)
itunesstored & lt;4e0cd9f873de3435b4119c48b2d6d13d& gt; 1761 1761 [ vm] ( daemon) ( idle)
softwareupdatese & lt;2bc4b5ae016431c98d3b34f81027d0ae& gt; 311 311 [ vm] ( daemon) ( idle)
Amazon & lt;4600481f07ec3e59a925319b7f67ba14& gt; 2951 2951 [ vm] ( suspended)
accountsd & lt;ac0fce15c1a2350d951efc498d521ac7& gt; 519 519 [ vm] ( daemon) ( idle)
coresymbolicatio & lt;edba67001f76313b992056c712153b4b& gt; 126 126 [ vm] ( daemon) ( idle)
Skype & lt;504cf2fe60cb3cdea8273e74df09836b& gt; 3187 3187 [ vm] ( background)
MobileMail & lt;bff817c61ce33c85a43ea9a6c98c29f5& gt; 14927 14927 [ vm] ( continuous)
MobileSMS & lt;46778de076363d67aeea207464cfc581& gt; 2134 2134 [ vm] ( background)
MobilePhone & lt;3fca241f2a193d0fb8264218d296ea41& gt; 2689 2689 [ vm] ( continuous)
librariand & lt;c9a9be81aa9632f0a913ce79b911f27e& gt; 317 317 [ vm] ( daemon)
kbd & lt;3e7136ddcefc3d77a01499db593466cd& gt; 616 616 [ vm] ( daemon)
tccd & lt;eb5ddcf533663f8d987d67cae6a4c4ea& gt; 224 224 [ vm] ( daemon)
Rage Masters & lt;90b45d6281e934209c5b06cf7dc4d492& gt; 28591 28591 [ vm] ( frontmost) ( resume)
ptpd & lt;04a56fce67053c57a7979aeea8e5a7ea& gt; 879 879 ( daemon)
iaptransportd & lt;f784f30dc09d32078d87b450e8113ef6& gt; 230 230 ( daemon)
locationd & lt;892cd1c9ffa43c99a82dba197be5f09e& gt; 1641 1641 ( daemon)
syslogd & lt;cbef142fa0a839f0885afb693fb169c3& gt; 237 237 ( daemon)
mediaserverd & lt;80657170daca32c9b8f3a6b1faac43a2& gt; 4869 4869 ( daemon)
dataaccessd & lt;2a3f6a518f3f3646bf35eddd36f25005& gt; 1786 1786 ( daemon)
aosnotifyd & lt;d4d14f2914c3343796e447cfef3e6542& gt; 549 549 ( daemon)
wifid & lt;9472b090746237998cdbb9b34f090d0c& gt; 455 455 ( daemon)
SpringBoard & lt;27372aae101f3bbc87804edc10314af3& gt; 18749 18749
backboardd & lt;5037235f295b33eda98eb5c72c098858& gt; 5801 5801 ( daemon)
UserEventAgent & lt;6edfd8d8dba23187b05772dcdfc94f90& gt; 601 601 ( daemon)
mediaremoted & lt;4ff39c50c684302492e396ace813cb25& gt; 293 293 ( daemon)
pasteboardd & lt;8a4279b78e4a321f84a076a711dc1c51& gt; 176 176 ( daemon)
springboardservi & lt;ff6f64b3a21a39c9a1793321eefa5304& gt; 0 0 ( daemon)
syslog_relay & lt;45e9844605d737a08368b5215bb54426& gt; 0 0 ( daemon)
DTMobileIS & lt;23303ca402aa3705870b01a9047854ea& gt; 0 0 ( daemon)
notification_pro & lt;845b7beebc8538ca9ceef731031983b7& gt; 169 169 ( daemon)
syslog_relay & lt;45e9844605d737a08368b5215bb54426& gt; 0 0 ( daemon)
ubd & lt;74dc476d1785300e9fcda555fcb8d774& gt; 976 976 ( daemon)
twitterd & lt;4b4946378a9c397d8250965d17055b8e& gt; 730 730 ( daemon)
configd & lt;4245d73a9e96360399452cf6b8671844& gt; 809 809 ( daemon)
absinthed.N94 & lt;7f4164c844fa340caa940b863c901aa9& gt; 99 99 ( daemon)
filecoordination & lt;fbab576f37a63b56a1039153fc1aa7d8& gt; 226 226 ( daemon)
distnoted & lt;a89af76ec8633ac2bbe99bc2b7964bb0& gt; 137 137 ( daemon)
apsd & lt;94d8051dd5f5362f82d775bc279ae608& gt; 373 373 ( daemon)
networkd & lt;0032f46009f53a6c80973fe153d1a588& gt; 219 219 ( daemon)
aggregated & lt;8c3c991dc4153bc38aee1e841864d088& gt; 112 112 ( daemon)
BTServer & lt;c92fbd7488e63be99ec9dbd05824f5e5& gt; 522 522 ( daemon)
fairplayd.N94 & lt;7bd896bd00783a48906090d05cf1c86a& gt; 210 210 ( daemon)
fseventsd & lt;996cc4ca03793184aea8d781b55bce08& gt; 384 384 ( daemon)
imagent & lt;1e68080947be352590ce96b7a1d07b2f& gt; 586 586 ( daemon)
mDNSResponder & lt;3e557693f3073697a58da6d27a827d97& gt; 295 295 ( daemon)
lockdownd & lt;ba1358c7a8003f1b91af7d5f58dd5bbe& gt; 389 389 ( daemon)
powerd & lt;2d2ffed5e69638aeba1b92ef124ed861& gt; 174 174 ( daemon)
CommCenter & lt;1f425e1e897d32e8864fdd8eeaa803a8& gt; 2212 2212 ( daemon)
notifyd & lt;51c0e03da8a93ac8a595442fcaac531f& gt; 211 211 ( daemon)
ReportCrash & lt;8c32f231b2ed360bb151b2563bcaa363& gt; 337 337 ( daemon)
这日志跟我们前面见到的相差很多。
这个一个来自iOS 6的低内存崩溃日志。正如我们前面所说的,低内存崩溃日志与其他类型的崩溃日志很不一样,它们不指向特定的文件和代码行。相反,它们画出了闪退时设备上的内存使用情况的图表。
至少,头部还是跟其他崩溃日志很像的: 提供了 Incident Identifier, CrashReporter Key, Hardware Model, OS Version等信息。
接下来部分是低内存崩溃日志特有的:
Free pages 指可用内存页数。每页大小约是4KB, 上面的日志中,可用内存约为3,872 KB (或者说 3.9 MB)。 Purgeable pages 是那部分可被清除或重用的内存。在上面的日志中,是0KB。 Largest process 是闪退时使用大部分内存的应用名称,在上面的日志中,正是你的应用! Processes 显示了闪退时各进程列表,还包含内存使用量。包含进程名 (第一列), 进程唯一标识符(第二名), 进程使用的内存页数(第三列)。最后一列是每个应用的状态。通常,发生闪退的应用的状态是 frontmost。 这里是 Rage Masters, 使用28591 页 (or 114.364 MB) 内存——这内存太多了!
通过,最大进程和frontmost状态的应用是相同的, 而且也是引起低内存闪退的应用进程。但是也可能看到最大进程和 frontmost状态应用不同的例子。比如,如果最大进程是SpringBoard, 忽略它 , 因为 SpringBoard 进程是显示主屏幕的应用,出现在你双击home按钮等情况,而且它是一直活动的。 低内存发生时,iOS向活动的应用发出低内存警告并终止后台应用。如果前台应用仍然继续增长内存,iOS将终止它。 为了查找低内存问题的原因,你必需使用Instruments剖析应用。如果你不知道怎么做,可以看一下我们 一篇关于这个方面的教程.。 :] 另外, 你也可以走捷径,响应低内存警告通知,以解决部分闪退问题。 回到Xcode查看RMLollipopLicker.m文件。 这是实现吃棒棒糖的视图控制器。看看源代码:
#import "RMLollipopLicker.h"
#define COUNT 20
@interface RMLollipopLicker ( )
@property ( weak, nonatomic) IBOutlet UIProgressView * progressView;
@property ( weak, nonatomic) IBOutlet UILabel * label;
@property ( weak, nonatomic) IBOutlet UILabel * lickedTimeLabel;
@end
@implementation RMLollipopLicker {
NSOperationQueue * queue;
NSMutableArray * lollipops;
}
#pragma mark - Life cycle
- ( void ) viewDidLoad {
[ super viewDidLoad] ;
self.progressView.progress = 0.0 ;
self.label.text = [ NSString stringWithFormat: @ "Tap on run and I'll lick a lollipop %d times!" , COUNT] ;
self.lickedTimeLabel.text = @ "" ;
lollipops = [ [ NSMutableArray alloc] init] ;
queue = [ [ NSOperationQueue alloc] init] ;
}
- ( void ) lickLollipop {
NSURL * fileURL = [ [ NSBundle mainBundle] URLForResource: @ "Lollipop" withExtension: @ "plist" ] ;
NSDictionary * dictionary = [ NSDictionary dictionaryWithContentsOfURL: fileURL] ;
NSString * lollipop = [ dictionary objectForKey: @ "Lollipop" ] ;
[ lollipops addObject: lollipop] ;
}
#pragma mark - IBActions
- ( IBAction) doneButtonPressed: ( id ) sender {
[ self dismissViewControllerAnimated: YES completion: nil ] ;
}
- ( IBAction) runButtonPressed: ( id ) sender {
[ sender setEnabled: NO ] ;
[ queue addOperationWithBlock:^ {
for ( NSInteger i = 0 ; i = COUNT) {
self.label.text = [ NSString stringWithFormat: @ "Tap on run and I'll lick a lollipop %d times!" , COUNT] ;
self.progressView.progress = 0.0 ;
[ sender setEnabled: YES ] ;
}
} ] ;
}
} ] ;
}
@end
当用户点击运行按钮, 应用开始一个背景线程,调用 lickLollipop 方法若干次,然后更新界面反映吃棒棒糖的数量。 lickLollipop 方法从属性列表文件(PLIST)文件读取一个长字符串,然后添加到数组上。这些数据并不重要, 能在不影响用户体验的前提下重新创建。 利用每种能够清除和重建数据而不影响用户体验的情况是好习惯。这样能够方便地释放内存,减少低内存警告。 那么,如何提高代码质量呢? 实现 didReceiveMemoryWarning 方法,像下面这样处理数据:
- ( void ) didReceiveMemoryWarning {
[ lollipops removeAllObjects] ;
[ super didReceiveMemoryWarning] ;
}
搞定!