标签:
http://www.raywenderlich.com/23704/demystifying-ios-application-crash-logs
This is a blog post by Soheil Moayedi Azarpour, an independent iOS developer. You can also find him on Google+.
Have you ever had the following experience as an app developer?
Before you submit your app, you perform a lot of testing to make sure your app runs flawlessly. It works fine on your device, but after the app is in the App Store, some users report crashes!
If you’re anything like me, you want your app to be A+. So you go back to your code to fix the crashes… but where do you look?
This is when iOS crash logs come in handy. In most cases, you’ll get very detailed and useful information about the cause of the crash, like feedback from a good teacher.
In this tutorial, you’ll learn about some common crash log scenarios, as well as how to acquire crash logs from development devices and iTunes Connect. You will learn about symbolication, and tracing back from log to code. You will also debug an application that can crash in certain situations.
OK, let’s get crashing!
When an application crashes on an iOS device, the operating system creates a crash report or a crash log. The report is stored on the device.
You can find a lot of useful information in a crash log, including the conditions under which the application terminated. Usually, there is a complete stack trace of each executing thread, so you can see what was happening in every thread at the time of the crash, and identify the thread where the crash occurred.
There are many ways to get a crash log from a device.
A device that is synced with iTunes stores its crash logs on the machine. Depending on the OS, here are the locations where you can find them:
Mac OS X:
~/Library/Logs/CrashReporter/MobileDevice/<DEVICE_NAME> |
Windows XP:
C:\Documents and Settings\<USERNAME>\Application Data\Apple Computer\Logs\CrashReporter\MobileDevice\<DEVICE_NAME> |
Windows Vista or 7:
C:\Users\<USERNAME>\AppData\Roaming\Apple Computer\Logs\CrashReporter\MobileDevice\<DEVICE_NAME> |
When your users experience a crash, you can instruct them to sync their device with iTunes, go to one of the above locations (depending on their OS), download the crash logs and email them to you.
You want any crash logs your users generate. The more crash logs you have, the better equipped you are to find and diagnose weaknesses in your app!
Also, you can easily get the crash reports for your devices via Xcode, if you have it installed. To do this, connect the iOS device to your computer and open Xcode. From the menu bar, select Window, then Organizer (the shortcut is Shift-CMD-2).
In the Organizer window, go to the Devices tab. On the left-hand side navigation pane, look for Device Logs, as shown in the image below:
As you can see in the screenshot, there are multiple Device Logs items. The Device Logs under LIBRARY contain all the logs from all devices you own (or have connected to Xcode). The Device Logs entries under each device are for that particular device.
Once your app is submitted, you can also get crash logs from your users using iTunes Connect. To do this, simply sign in to your iTunes Connect account, navigate to Manage Your Applications, click on the app you want crash logs for, click on the View Details button below the icon, and click on Crash Reports in the right-hand side pane in the Links section.
If there are no crash logs, try clicking on the Refresh button. If you haven’t sold many copies of your app, or if it has been available only for a short time, chances are that you don’t have any crash logs in your iTunes Connect account.
If you did have any crash logs in iTunes Connect, you would see something like this:
Sometimes you won’t see anything in here despite your users reporting a crash. In that case, it’s best to ask them to send you the crash logs if possible.
There are two main situations that can result in a crash log:
iOS policy violations include things such as watchdog timeout at the time of launch, resume, suspend and quit; user force-quit; and low memory termination. Let’s go over these in more detail.
Watchdog Timeout
As you’re probably aware, since iOS 4.x, most of the time when you quit an iOS app, the app isn’t terminated – instead, it’s sent to the background.
However, there are times when the OS will terminate your app and generate a crash log if the app didn’t respond fast enough. These events correspond with the implementation of the following UIApplicationDelegate methods:
In all of the above methods, the app gets a limited amount of time to finish its processing. If the app takes too long, the OS will terminate the app.
Note: This can easily happen to you if you aren’t performing long running operations (like network access) on background threads. For information on how to avoid this, check out our Grand Central Dispatch andNSOperations tutorials.
User Force-Quit
iOS 4.x supports multitasking. If an app blocks the UI and stops responding, the user can double-tap the Home button from the Home screen and terminate the app. In this case, the OS generates a crash log.
Note: You may have noticed that when you double-tap the Home button, you also get a list of all the applications you’ve run in the past. Those apps are not necessarily running, nor are they necessarily suspended.
Usually an app gets about 10 minutes to stay in the background once the user hits the Home button, and then it gets terminated automatically by the OS. So the list of apps that you see by double-tapping the Home button is only a list of past app runs. Deleting the icons for those apps does not generate any crash logs.
Low Memory Termination
When subclassing UIViewController, you may have noticed the didReceiveMemoryWarning method.
Any app that is running in the foreground has the highest priority in terms of accessing and using memory. However, that does not mean the app gets all the available memory on the device – each app gets a portion of the available memory.
When total memory consumption hits a certain level, the OS sends out aUIApplicationDidReceiveMemoryWarningNotification notification. At the same time,didReceiveMemoryWarning is invoked for the app.
At this point, so that your app continues to run properly, the OS begins terminating apps in the background to free some memory. Once all background apps are terminated, if your app still needs more memory, the OS terminates your app and generates a crash log.
The OS does not generate a crash log for the background apps that were terminated under this circumstance.
Note: Per Apple documentation, Xcode does not add low memory logs automatically. You have to obtain them manually, as described above. However, in my personal experience using Xcode 4.5.2, low memory crash logs were imported automatically with “Process” and “Type” listed as unknown.
It is also worth mentioning that allocating a big chunk of memory in a very short period of time puts a strain on the system memory — even if for a fraction of a second. This, too, can result in memory warning notifications.
Bugs in the Application
As you can imagine, these cause the majority of app crashes, and hence, are behind the generation of most crash logs. Bugs come in seemingly endless varieties.
Later on in this tutorial, you will confront some actual crash logs from a buggy application, and use your powers of deduction to find the culprits and apply some fixes!
Let’s start by taking a look at a sample crash log, so that you have an idea of what to expect before tackling some real scenarios.
Without further ado, meet your new friend:
// 1: Process Information Incident Identifier: 30E46451-53FD-4965-896A-457FC11AD05F CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31 Hardware Model: iPhone4,1 Process: Rage Masters [4155] Path: /var/mobile/Applications/A5635B22-F5EF-4CEB-94B6-FE158D885014/Rage Masters.app/Rage Masters Identifier: Rage Masters Version: ??? (???) Code Type: ARM (Native) Parent Process: launchd [1] // 2: Basic Information Date/Time: 2012-10-17 21:39:06.967 -0400 OS Version: iOS 6.0 (10A403) Report Version: 104 // 3: Exception Exception Type: 00000020 Exception Codes: 0x000000008badf00d Highlighted Thread: 0 // 4: Threads backtraces Thread 0 name: Dispatch queue: com.apple.main-thread Thread 0: 0 libsystem_kernel.dylib 0x327f2eb4 mach_msg_trap + 20 1 libsystem_kernel.dylib 0x327f3048 mach_msg + 36 2 CoreFoundation 0x36bd4040 __CFRunLoopServiceMachPort + 124 3 CoreFoundation 0x36bd2d9e __CFRunLoopRun + 878 4 CoreFoundation 0x36b45eb8 CFRunLoopRunSpecific + 352 5 CoreFoundation 0x36b45d44 CFRunLoopRunInMode + 100 6 CFNetwork 0x32ac343e CFURLConnectionSendSynchronousRequest + 330 7 Foundation 0x346e69ba +[NSURLConnection sendSynchronousRequest:returningResponse:error:] + 242 8 Rage Masters 0x000d4046 0xd2000 + 8262 Thread 1: 0 libsystem_kernel.dylib 0x32803d98 __workq_kernreturn + 8 1 libsystem_c.dylib 0x3a987cf6 _pthread_workq_return + 14 2 libsystem_c.dylib 0x3a987a12 _pthread_wqthread + 362 3 libsystem_c.dylib 0x3a9878a0 start_wqthread + 4 // 5: Thread state Thread 0 crashed with ARM Thread State (32-bit): r0: 0x00000000 r1: 0x00000000 r2: 0x00000001 r3: 0x39529fc8 r4: 0xffffffff r5: 0x2fd7d301 r6: 0x2fd7d300 r7: 0x2fd7d9d0 r8: 0x2fd7d330 r9: 0x3adbf8a8 r10: 0x2fd7d308 r11: 0x00000032 ip: 0x00000025 sp: 0x2fd7d2ec lr: 0x001bdb25 pc: 0x30301838 cpsr: 0x00000010 // 6: Binary images Binary Images: 0xd2000 - 0xd7fff +Rage Masters armv7 <f37ee6d2c7b334868972e0e9c54f7062> /var/mobile/Applications/A5635B22-F5EF-4CEB-94B6-FE158D885014/Rage Masters.app/Rage Masters 0x2fe41000 - 0x2fe61fff dyld armv7 <75594988728831d98e1f7c4c7b7ca29d> /usr/lib/dyld 0x327f2000 - 0x32808fff libsystem_kernel.dylib armv7 <f167dacec44b3a86a8eee73400ff7a83> /usr/lib/system/libsystem_kernel.dylib 0x328a8000 - 0x328bdfff libresolv.9.dylib armv7 <e79b59a3406f34d9b37f8085955115ce> /usr/lib/libresolv.9.dylib 0x32a70000 - 0x32b35fff CFNetwork armv7 <3e973794a4d13428bb974edcb2027139> /System/Library/Frameworks/CFNetwork.framework/CFNetwork 0x32b7a000 - 0x32cc3fff libicucore.A.dylib armv7 <0253932c1b9038a0849ef73c38e076ca> /usr/lib/libicucore.A.dylib 0x32cc4000 - 0x32cc5fff CoreSurface armv7 <b3f9d4e8dd803a48b88c58a0663d92a3> /System/Library/PrivateFrameworks/CoreSurface.framework/CoreSurface 0x32f65000 - 0x32f8afff OpenCL armv7 <f7706501012430fc94ed99006419fba9> /System/Library/PrivateFrameworks/OpenCL.framework/OpenCL |
There is a lot of mysterious stuff in this report. :] Let’s go through it section-by-section:
(1) Process Information
This first section gives you some information about the process that crashed.
(2) Basic Information
This section gives you some basic information about the date and time of the crash, and the version of iOS running on the device. If you have a lot of crash logs coming from iOS 6.0, it might mean that your problem is specific to iOS 6.
(3) Exception
In this section, you can see the type of exception that was thrown at the time of the crash. You also get the exception code and the thread that threw the exception. Depending on the type of crash report, you may get some extra information in this section as well.
(4) Threads backtraces
This section provides the backtrace log for all threads in the app. Backtrace is a list of all active frames at the time of the crash. It gives you a list of function calls when the crash happened. Consider the line below:
2 XYZLib 0x34648e88 0x83000 + 8740 |
It is basically four columns:
(5) Thread state
This section gives you the values in the registers at the time of crash. You don’t usually need this section, because the backtrace has already given you the information you need to find your problem.
(6) Binary images
This section lists all the binaries that were loaded at the time of the crash.
When you first look at the backtrace in a crash log, it doesn’t make sense. You’re used to working with function names and line numbers, not a cryptic location like this:
6 Rage Masters 0x0001625c 0x2a000 + 30034 |
The process of converting from these hexidecimal addresses in the executable code to method names and line numbers is called symbolification.
When you get crash logs off of a device through Xcode’s Organizer window, they are automatically symbolicated after a few seconds. The symbolicated version of the above line is this:
6 Rage Masters 0x0001625c -[RMAppDelegate application:didFinishLaunchingWithOptions:] (RMAppDelegate.m:35) |
For Xcode to symbolicate a crash log, it needs to have access to the matching application binary that was uploaded to the App Store, and the .dSYM file that was generated when that binary was built. This must be an exact match; otherwise, the report cannot be fully symbolicated.
So, it is essential that you keep each build distributed to users. When you archive your app before submission, Xcode stores your binary. You can find all of your archived applications in the Xcode Organizer under the Archivestab.
Xcode will automatically symbolicate all crash reports that it encounters, if it has the matching .dSYM and application binary that produced the crash report. If you are switching computers or creating a new account, make sure you move over all of those binaries and put them in the right place, where Xcode can find them.
Note: You must keep both the application binary and the .dSYM file to be able to fully symbolicate crash reports. You should archive these files for every build that you submit to iTunes Connect.
The .dSYM and application binary are specifically tied together on a per-build-basis, and subsequent builds, even from the same source files, will not interoperate with files from other builds.
If you use the Build and Archive command, the files will be placed in a suitable location automatically. Otherwise, any location searchable by Spotlight (such as your home directory) is fine.
Since low memory crash logs are slightly different than normal crash logs, this tutorial will address them separately. :]
When a low memory condition is detected on an iOS device, the virtual memory system sends out notifications asking applications to release memory. These notifications are sent to all running applications and processes, in an effort to reduce the total amount of memory in use.
If memory usage remains high, the system may terminate background processes to ease memory pressure. If enough memory can be freed, your application will continue to run and no crash report will be generated. Otherwise, your app will be terminated by iOS, and a low memory report will be generated.
In low memory crash logs, there are no stack traces for the application threads. Instead, the memory usage of each process is reported in terms of the number of memory pages. (At the time of writing, a memory page was 4KB in size.)
You will see jettisoned next to the name of any process terminated by iOS to free up memory. If you see it next to your application’s name, it means the application was terminated for using too much memory.
A low memory crash log looks something like this:
Incident Identifier: 30E46451-53FD-4965-896A-457FC11AD05F CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31 OS Version: iPhone OS 3.1.3 (7E18) Date/Time: 2012-10-17 21:39:06.967 -0400 Free pages: 96 Wired pages: 10558 Purgeable pages: 0 Largest process: Rage Masters Processes Name UUID Count resident pages Rage Masters <cc527ca9b51937c5adbe035fe27a7b12> 9320 (jettisoned) (active) mediaserverd <3d3800d6acfff050e4d0ed91cbe2467e> 255 dataaccessd <13d80b2e707acc91f9aa3ec4c715b9cc> 505 syslogd <8eddddc00294d5615afded36ee3f1b62> 71 apsd <32070d91b216d806973c8f1b1d8077a4> 171 securityd <b9e51062610d27f727c5119b8f80dcdf> 243 notifyd <591dd4dd804b4b8741f52335ea1fa4ab> 2027 CommCenter <b4b87526ae086bb62c982f1078f43f81> 189 SpringBoard <324939a437d1cca1fa4af72d9f5d0eba> 2158 (active) accessoryd <8f21c8b376d16e2ccb95ed6d21d8317a> 91 configd <85efd41aceac34ccc0019df76623c7a9> 371 fairplayd <a2eaf736b3e07c7c9a2c82e9eb893555> 93 mDNSResponder <df1cd275e4ad434e0575990e8e1da4cb> 292 lockdownd <80d2bd44c0bcca273d48ce52010f7e65> 1204 launchd <a5988245aade809bf77576f1d9de42c5> 72 |
When you experience a low memory crash in your app, you should investigate memory usage patterns and how your app responds to low memory warnings. You can use the Allocations and Leaks Instruments to discover memory allocation issues and leaks. If you don’t know how to use Instruments to check for memory issues, you might want to check out this tutorial as a starting point.
And don’t forget virtual memory! The Leaks and Allocations Instruments do not track graphics memory. You need to run your application with the VM Tracker Instrument to see your graphics memory usage.
VM Tracker is disabled by default. To profile your application with VM Tracker, click the Instrument, and check theAutomatic Snapshotting flag or press the Snapshot Now button manually.
You will investigate a low memory crash log later on in this tutorial.
Before you dive into some real-life crash scenarios, there’s one more bit about crash log content that warrants a brief introduction: those funny exception codes.
You can find the exception code in the Exception section of the report – section #3 in the above example. There are a few well-known exception codes that you might encounter often.
Usually, an exception code starts with some text, followed by one or more hexadecimal values, which are processor-specific codes that may give you more information on the nature of the crash. From these codes, you can tell if the application crashed due to a programming error, a bad memory access, or if the application was terminated for some other reason.
Here are some of the more common exception codes:
Note: Remember that terminating a suspended app by removing it from the background task list does not generate a crash log. Once an app is suspended, it is eligible for termination by iOS at any time. So no crash log will be generated.
All right! You’ve got all the basic background information you need to dive and swim in the pool of crash logs, and fix some nasty bugs! Here is the basic scenario:
You have just started a new job at Rage-O-Rage LLC. The company has a top-selling app in the App Store, Rage Masters.
Your boss, Andy, comes to you and says that to get you started, he has decided to give you a list of different scenarios under which users have claimed the app crashes. Your job is to go through the scenarios and the symbolicated crash logs provided by users, find the bugs in the app, and fix them.
You can download the source code for the app from here.
Note: If you want to regenerate the crash reports for yourself, follow these instructions:
From a user email: “Man, your app is a piece of junk! I downloaded it on my iPod Touch, iPhone and my son’s iPod Touch. On all devices, it crashed before running on the first try…”
Another email says, “I downloaded your app and it crashed. Very unhappy…”
Another email is more specific: “I can’t get your app to run. I downloaded it on all of my devices and on my wife’s. On all of them, it crashed on the first launch…”
OK, don’t take this lying down! Do any of these comments give you a hint? Take a look at the crash log:
Incident Identifier: 85833DBA-3DF7-43EE-AF80-4E5C51091F42 CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31 Hardware Model: iPhone4,1 Process: Rage Masters [20067] Path: /var/mobile/Applications/B2121A89-3D1F-4E61-BB18-5511E1DC150F/Rage Masters.app/Rage Masters Identifier: Rage Masters Version: ??? (???) Code Type: ARM (Native) Parent Process: launchd [1] Date/Time: 2012-11-03 13:37:31.148 -0400 OS Version: iOS 6.0 (10A403) Report Version: 104 Exception Type: 00000020 Exception Codes: 0x000000008badf00d Highlighted Thread: 0 Application Specific Information: Soheil-Azarpour.Rage-Masters failed to launch in time Elapsed total CPU time (seconds): 8.030 (user 8.030, system 0.000), 20% CPU Elapsed application CPU time (seconds): 3.840, 10% CPU Thread 0 name: Dispatch queue: com.apple.main-thread Thread 0: 0 libsystem_kernel.dylib 0x327f2eb4 mach_msg_trap + 20 1 libsystem_kernel.dylib 0x327f3048 mach_msg + 36 2 CoreFoundation 0x36bd4040 __CFRunLoopServiceMachPort + 124 3 CoreFoundation 0x36bd2d9e __CFRunLoopRun + 878 4 CoreFoundation 0x36b45eb8 CFRunLoopRunSpecific + 352 5 CoreFoundation 0x36b45d44 CFRunLoopRunInMode + 100 6 CFNetwork 0x32ac343e CFURLConnectionSendSynchronousRequest + 330 7 Foundation 0x346e69ba +[NSURLConnection sendSynchronousRequest:returningResponse:error:] + 242 8 Rage Masters 0x000ea1c4 -[RMAppDelegate application:didFinishLaunchingWithOptions:] (RMAppDelegate.m:36) 9 UIKit 0x37f30ad4 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 248 10 UIKit 0x37f3065e -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1186 11 UIKit 0x37f28846 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 694 12 UIKit 0x37ed0c3c -[UIApplication handleEvent:withNewEvent:] + 1000 13 UIKit 0x37ed06d0 -[UIApplication sendEvent:] + 68 14 UIKit 0x37ed011e _UIApplicationHandleEvent + 6150 15 GraphicsServices 0x370835a0 _PurpleEventCallback + 588 16 GraphicsServices 0x370831ce PurpleEventCallback + 30 17 CoreFoundation 0x36bd4170 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 32 18 CoreFoundation 0x36bd4112 __CFRunLoopDoSource1 + 134 19 CoreFoundation 0x36bd2f94 __CFRunLoopRun + 1380 20 CoreFoundation 0x36b45eb8 CFRunLoopRunSpecific + 352 21 CoreFoundation 0x36b45d44 CFRunLoopRunInMode + 100 22 UIKit 0x37f27480 -[UIApplication _run] + 664 23 UIKit 0x37f242fc UIApplicationMain + 1116 24 Rage Masters 0x000ea004 main (main.m:16) 25 libdyld.dylib 0x3b630b1c start + 0 |
Did you find the problem? The exception code is 0x000000008badf00d, and right after that, the report says:
Application Specific Information: Soheil-Azarpour.Rage-Masters failed to launch in time Elapsed total CPU time (seconds): 8.030 (user 8.030, system 0.000), 20% CPU Elapsed application CPU time (seconds): 3.840, 10% CPU |
This means that the app failed to launch in time, and the iOS watchdog terminated the app. Cool! You found the reason, but why (and more importantly, where) is it happening?
Look further down in the log. The convention is to read the backtrace log from the bottom up. The lowest frame (frame 25: libdyld.dylib) is the first call, then frame 24, Rage Masters, main (main.m:16) is called from there, and so on.
You are interested in frames that are related to your app’s code. So ignore system libraries and frameworks. The next line relevant to your code is:
8 Rage Masters 0x0009f244 -[RMAppDelegate application:didFinishLaunchingWithOptions:] (RMAppDelegate.m:35) |
The app failed in application:didFinishLaunchingWithOptions:, on line 35 in RMAppDelegate (RMAppDelegate.m:35). Open Xcode and take a look at that line:
NSData *directoryData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]; |
Yep, there it is! A synchronous call to a web service?! On the main thread?! Inapplication:didFinishLaunchingWithOptions:?!! Who wrote this code?!
Anyway, it’s your job to fix it. This call should be asynchronous and, even better, executed from another part of the application, after application:didFinishLaunchingWithOptions: has returned YES.
It might require more extensive changes to move the call elsewhere. So for the moment, just take care of the code so that it doesn’t crash. You can always go back and implement a better design later. Replace the line of offending code from above (and the three lines following it) with an asynchronous version:
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { NSURL *cacheDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSUserDirectory inDomains:NSCachesDirectory] lastObject]; NSURL *filePath = [NSURL URLWithString:kDirectoryFile relativeToURL:cacheDirectory]; [data writeToFile:[filePath absoluteString] atomically:YES]; }]; |
A user writes: “I can’t bookmark a rage master as my favorite. When I try to, the app crashes…”
Another user says, “Bookmarking doesn’t work… I go to detail info, tap on bookmark button and BOOM!”
The above complaints don’t say much, and there could be any number of causes for the issue they’ve identified. Take a look at the crash log:
Incident Identifier: 3AAA63CC-3088-41CC-84D9-82FE03F9F354 CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31 Hardware Model: iPhone4,1 Process: Rage Masters [20090] Path: /var/mobile/Applications/B2121A89-3D1F-4E61-BB18-5511E1DC150F/Rage Masters.app/Rage Masters Identifier: Rage Masters Version: ??? (???) Code Type: ARM (Native) Parent Process: launchd [1] Date/Time: 2012-11-03 13:39:00.081 -0400 OS Version: iOS 6.0 (10A403) Report Version: 104 Exception Type: EXC_CRASH (SIGABRT) Exception Codes: 0x0000000000000000, 0x0000000000000000 Crashed Thread: 0 Last Exception Backtrace: 0 CoreFoundation 0x36bff29e __exceptionPreprocess + 158 1 libobjc.A.dylib 0x34f0f97a objc_exception_throw + 26 2 CoreFoundation 0x36c02e02 -[NSObject(NSObject) doesNotRecognizeSelector:] + 166 3 CoreFoundation 0x36c0152c ___forwarding___ + 388 4 CoreFoundation 0x36b58f64 _CF_forwarding_prep_0 + 20 5 UIKit 0x37fbb0a8 -[UIApplication sendAction:to:from:forEvent:] + 68 6 UIKit 0x37fbb05a -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 26 7 UIKit 0x37fbb038 -[UIControl sendAction:to:forEvent:] + 40 8 UIKit 0x37fba8ee -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 498 9 UIKit 0x37fbade4 -[UIControl touchesEnded:withEvent:] + 484 10 UIKit 0x37ee35f4 -[UIWindow _sendTouchesForEvent:] + 520 11 UIKit 0x37ed0804 -[UIApplication sendEvent:] + 376 12 UIKit 0x37ed011e _UIApplicationHandleEvent + 6150 13 GraphicsServices 0x3708359e _PurpleEventCallback + 586 14 GraphicsServices 0x370831ce PurpleEventCallback + 30 15 CoreFoundation 0x36bd416e __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 30 16 CoreFoundation 0x36bd4112 __CFRunLoopDoSource1 + 134 17 CoreFoundation 0x36bd2f94 __CFRunLoopRun + 1380 18 CoreFoundation 0x36b45eb8 CFRunLoopRunSpecific + 352 19 CoreFoundation 0x36b45d44 CFRunLoopRunInMode + 100 20 GraphicsServices 0x370822e6 GSEventRunModal + 70 21 UIKit 0x37f242fc UIApplicationMain + 1116 22 Rage Masters 0x000ca004 main (main.m:16) 23 libdyld.dylib 0x3b630b1c start + 0 |
The exception code is a SIGABRT. Usually, a SIGABRT exception is raised when an object receives an unimplemented message. Or to put it in simpler terms, there’s a call for a nonexistent method on an object.
Usually this won’t happen, since if you call method “foo” on object “bar”, the compiler will throw an error if the method “foo” doesn’t exist. But when you indirectly call a method using a selector, the compiler might not be able to determine whether or not the method exists on the object.
Back to the crash log. It indicates that the crashed thread is 0. This means that you’re probably looking at a situation where a method was called on an object on the main thread, where the object did not implement the method.
If you continue reading the backtrace log, you see that the only call related to your code is frame 22, main.m:16. That doesn’t help much. :[
Keep going up the framework calls, and there it is:
That isn’t your code. But at least it confirms that there was a call to an unimplemented method on an object.
Go to RMDetailViewController.m, since that’s where the bookmark button is implemented. Find the code that bookmarks the rage master:
-(IBAction)bookmarkButtonPressed { self.master.isBookmarked = !self.master.isBookmarked; // Update shared bookmarks if (self.master.isBookmarked) [[RMBookmarks sharedBookmarks] bookmarkMaster:self.master]; else [[RMBookmarks sharedBookmarks] unbookmarkMaster:self.master]; // Update UI [self updateBookmarkImage]; } |
That looks OK, so check the storyboard (XIB file) and make sure that the button is hooked up correctly.
There it is! In MainStoryboard.storyboard, the button refers to bookmarkButtonPressed: instead of bookmarkButtonPressed (note the final colon indicating that the method expects a parameter). To fix this, replace the signature of the method above with this:
-(IBAction)bookmarkButtonPressed:(id)sender { // Remain unchanged... } |
Of course, you could also simply remove the incorrect connection in the XIB file and reconnect to the method, so that the method signature is correct in the XIB file. Either way works.
And that’s another crash fixed. You’re getting pretty good at this. :]
Another user complains saying, “I can’t delete bookmarked masters from the bookmarks view…” And another email about the same issue reads, “If I try to delete a master in bookmarks, the app crashes…”
By now, you are used to these emails not being very helpful. To the crash logs!
Incident Identifier: 5B62D681-D8FE-41FE-8D52-AB7E6D6B2AC7 CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31 Hardware Model: iPhone4,1 Process: Rage Masters [20088] Path: /var/mobile/Applications/B2121A89-3D1F-4E61-BB18-5511E1DC150F/Rage Masters.app/Rage Masters Identifier: Rage Masters Version: ??? (???) Code Type: ARM (Native) Parent Process: launchd [1] Date/Time: 2012-11-03 13:38:45.762 -0400 OS Version: iOS 6.0 (10A403) Report Version: 104 Exception Type: EXC_CRASH (SIGABRT) Exception Codes: 0x0000000000000000, 0x0000000000000000 Crashed Thread: 0 Last Exception Backtrace: 0 CoreFoundation 0x36bff29e __exceptionPreprocess + 158 1 libobjc.A.dylib 0x34f0f97a objc_exception_throw + 26 2 CoreFoundation 0x36bff158 +[NSException raise:format:arguments:] + 96 3 Foundation 0x346812aa -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 86 4 UIKit 0x37f04b7e -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:] + 7690 5 UIKit 0x3803a4a2 -[UITableView deleteRowsAtIndexPaths:withRowAnimation:] + 22 6 Rage Masters 0x000fd9ca -[RMBookmarksViewController tableView:commitEditingStyle:forRowAtIndexPath:] (RMBookmarksViewController.m:68) 7 UIKit 0x3809a5d4 -[UITableView(UITableViewInternal) animateDeletionOfRowWithCell:] + 80 8 UIKit 0x37fbb0a8 -[UIApplication sendAction:to:from:forEvent:] + 68 9 UIKit 0x37fbb05a -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 26 10 UIKit 0x37fbb038 -[UIControl sendAction:to:forEvent:] + 40 11 UIKit 0x37fba8ee -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 498 12 UIKit 0x37fbb0a8 -[UIApplication sendAction:to:from:forEvent:] + 68 13 UIKit 0x37fbb05a -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 26 14 UIKit 0x37fbb038 -[UIControl sendAction:to:forEvent:] + 40 15 UIKit 0x37fba8ee -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 498 16 UIKit 0x37fbade4 -[UIControl touchesEnded:withEvent:] + 484 17 UIKit 0x37ee35f4 -[UIWindow _sendTouchesForEvent:] + 520 18 UIKit 0x37ed0804 -[UIApplication sendEvent:] + 376 19 UIKit 0x37ed011e _UIApplicationHandleEvent + 6150 20 GraphicsServices 0x3708359e _PurpleEventCallback + 586 21 GraphicsServices 0x370831ce PurpleEventCallback + 30 22 CoreFoundation 0x36bd416e __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 30 23 CoreFoundation 0x36bd4112 __CFRunLoopDoSource1 + 134 24 CoreFoundation 0x36bd2f94 __CFRunLoopRun + 1380 25 CoreFoundation 0x36b45eb8 CFRunLoopRunSpecific + 352 26 CoreFoundation 0x36b45d44 CFRunLoopRunInMode + 100 27 GraphicsServices 0x370822e6 GSEventRunModal + 70 28 UIKit 0x37f242fc UIApplicationMain + 1116 29 Rage Masters 0x000fb004 main (main.m:16) 30 libdyld.dylib 0x3b630b1c start + 0 |
This looks very similar to the previous crash log. It’s another SIGABRT exception. Perhaps you wonder if it’s the same issue: sending a message to an object that has not implemented the method?
Let’s traverse through the backlog and see what methods were called. Start from the bottom. The last call to your code in Rage Masters is at frame 6:
6 Rage Masters 0x00088c66 -[RMBookmarksViewController tableView:commitEditingStyle:forRowAtIndexPath:] (RMBookmarksViewController.m:68) |
Well, this is a UITableViewDataSource method. Huh?! You are pretty sure Apple has implemented this method – you can override it, but it is not like it hasn’t been implemented. Plus, it is an optional delegate method. So the problem isn’t a call to an unimplemented method.
Take a look at the frames after that call:
3 Foundation 0x346812aa -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 86 4 UIKit 0x37f04b7e -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:] + 7690 5 UIKit 0x3803a4a2 -[UITableView deleteRowsAtIndexPaths:withRowAnimation:] + 22 |
In frame 5, UITableView is calling another method of its own, deleteRowsAtIndexPaths:withRowAnimation: and then _endCellAnimationsWithContext: is called, which looks like an internal Apple method. Then the Foundation framework raises an exception, handleFailureInMethod:object:file:lineNumber:description:.
Putting the above together with the user complaints, it looks as if you are dealing with a buggy deletion procedure in UITableView. Go to Xcode. Do you know where to go? Can you tell from the crash log? It’s line 68 inRMBookmarksViewController.m:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } |
Can you tell where the problem is? I’ll wait while you take a look.
You got it! How about the data source? The code deletes a row in the table view, but doesn’t change the data source behind it. To fix this, replace the above method with the following one:
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { RMMaster *masterToDelete = [bookmarks objectAtIndex:indexPath.row]; [bookmarks removeObject:masterToDelete]; [[RMBookmarks sharedBookmarks] unbookmarkMaster:masterToDelete]; [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } |
That should do it! Take that, you nasty bug!! BIFF!!! BANG!!!! CRASH!!!!!
The email says, “My app crashes when a rage master is licking a lollipop…” Another user writes, “I tell the rage master to lick the lollipop few times, and then the app crashes!”
Here’s the crash log:
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 <UUID> rpages recent_max [reason] (state) lsd <6a9f5b5f36b23fc78f87b6d8f1f49a9d> 331 331 [vm] (daemon) (idle) afcd <b0aff2e7952e34a9882fec81a8dcdbb2> 141 141 [vm] (daemon) (idle) itunesstored <4e0cd9f873de3435b4119c48b2d6d13d> 1761 1761 [vm] (daemon) (idle) softwareupdatese <2bc4b5ae016431c98d3b34f81027d0ae> 311 311 [vm] (daemon) (idle) Amazon <4600481f07ec3e59a925319b7f67ba14> 2951 2951 [vm] (suspended) accountsd <ac0fce15c1a2350d951efc498d521ac7> 519 519 [vm] (daemon) (idle) coresymbolicatio <edba67001f76313b992056c712153b4b> 126 126 [vm] (daemon) (idle) Skype <504cf2fe60cb3cdea8273e74df09836b> 3187 3187 [vm] (background) MobileMail <bff817c61ce33c85a43ea9a6c98c29f5> 14927 14927 [vm] (continuous) MobileSMS <46778de076363d67aeea207464cfc581> 2134 2134 [vm] (background) MobilePhone <3fca241f2a193d0fb8264218d296ea41> 2689 2689 [vm] (continuous) librariand <c9a9be81aa9632f0a913ce79b911f27e> 317 317 [vm] (daemon) kbd <3e7136ddcefc3d77a01499db593466cd> 616 616 [vm] (daemon) tccd <eb5ddcf533663f8d987d67cae6a4c4ea> 224 224 [vm] (daemon) Rage Masters <90b45d6281e934209c5b06cf7dc4d492> 28591 28591 [vm] (frontmost) (resume) ptpd <04a56fce67053c57a7979aeea8e5a7ea> 879 879 (daemon) iaptransportd <f784f30dc09d32078d87b450e8113ef6> 230 230 (daemon) locationd <892cd1c9ffa43c99a82dba197be5f09e> 1641 1641 (daemon) syslogd <cbef142fa0a839f0885afb693fb169c3> 237 237 (daemon) mediaserverd <80657170daca32c9b8f3a6b1faac43a2> 4869 4869 (daemon) dataaccessd <2a3f6a518f3f3646bf35eddd36f25005> 1786 1786 (daemon) aosnotifyd <d4d14f2914c3343796e447cfef3e6542> 549 549 (daemon) wifid <9472b090746237998cdbb9b34f090d0c> 455 455 (daemon) SpringBoard <27372aae101f3bbc87804edc10314af3> 18749 18749 backboardd <5037235f295b33eda98eb5c72c098858> 5801 5801 (daemon) UserEventAgent <6edfd8d8dba23187b05772dcdfc94f90> 601 601 (daemon) mediaremoted <4ff39c50c684302492e396ace813cb25> 293 293 (daemon) pasteboardd <8a4279b78e4a321f84a076a711dc1c51> 176 176 (daemon) springboardservi <ff6f64b3a21a39c9a1793321eefa5304> 0 0 (daemon) syslog_relay <45e9844605d737a08368b5215bb54426> 0 0 (daemon) DTMobileIS <23303ca402aa3705870b01a9047854ea> 0 0 (daemon) notification_pro <845b7beebc8538ca9ceef731031983b7> 169 169 (daemon) syslog_relay <45e9844605d737a08368b5215bb54426> 0 0 (daemon) ubd <74dc476d1785300e9fcda555fcb8d774> 976 976 (daemon) twitterd <4b4946378a9c397d8250965d17055b8e> 730 730 (daemon) configd <4245d73a9e96360399452cf6b8671844> 809 809 (daemon) absinthed.N94 <7f4164c844fa340caa940b863c901aa9> 99 99 (daemon) filecoordination <fbab576f37a63b56a1039153fc1aa7d8> 226 226 (daemon) distnoted <a89af76ec8633ac2bbe99bc2b7964bb0> 137 137 (daemon) apsd <94d8051dd5f5362f82d775bc279ae608> 373 373 (daemon) networkd <0032f46009f53a6c80973fe153d1a588> 219 219 (daemon) aggregated <8c3c991dc4153bc38aee1e841864d088> 112 112 (daemon) BTServer <c92fbd7488e63be99ec9dbd05824f5e5> 522 522 (daemon) fairplayd.N94 <7bd896bd00783a48906090d05cf1c86a> 210 210 (daemon) fseventsd <996cc4ca03793184aea8d781b55bce08> 384 384 (daemon) imagent <1e68080947be352590ce96b7a1d07b2f> 586 586 (daemon) mDNSResponder <3e557693f3073697a58da6d27a827d97> 295 295 (daemon) lockdownd <ba1358c7a8003f1b91af7d5f58dd5bbe> 389 389 (daemon) powerd <2d2ffed5e69638aeba1b92ef124ed861> 174 174 (daemon) CommCenter <1f425e1e897d32e8864fdd8eeaa803a8> 2212 2212 (daemon) notifyd <51c0e03da8a93ac8a595442fcaac531f> 211 211 (daemon) ReportCrash <8c32f231b2ed360bb151b2563bcaa363> 337 337 (daemon) |
This log is very different from what you’ve seen so far!
This is a low memory crash log from iOS 6. As discussed earlier, low memory crash logs are different from other types of crash logs because they don’t point to a specific file or line of code. Instead, they paint a picture of the memory situation on the device at the time of the crash.
The header, at least, is similar to that of other crash logs: it provides the Incident Identifier, CrashReporter Key, Hardware Model, OS Version, and some other information.
The next section is specific to low memory crash logs:
Usually, the largest process and the frontmost app are the same, and are also the process that has caused the low memory crash. But you may see some instances where the largest process and the frontmost app are not the same. For example, if the largest process is SpringBoard, ignore it, because SpringBoard is the process that shows the apps on the home screen, the popups that appear when you double tap the home button, etc. and is always active.
When low memory situations happen, iOS sends a low memory warning to the active application and terminates background processes. If the frontmost app still continues to grow in memory, iOS jettisons it.
To find the reason for the low memory issues, you need to profile your app using Instruments. You won’t be doing that here, since we already have a tutorial for that. :] Instead, you’ll take a shortcut and just respond to the low memory warning notification that the app receives to solve this particular crash.
Switch to Xcode and go to RMLollipopLicker.m. This is where the lollipop licker view controller is implemented. Take a look at the source code:
#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 ; i++) { [self lickLollipop]; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.label.text = [NSString stringWithFormat:@"Licked a strawberry lollipop %d time(s)!", i]; self.lickedTimeLabel.text = [NSString stringWithFormat:@"Licked the same lollipop %d time(s)!", lollipops.count-1]; self.progressView.progress = (float)(i/COUNT); if (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 |
When the user taps the run button, the app starts a background operation, calls lickLollipop a number of times, and then updates the UI to reflect the number of licks. lickLollipop reads a big NSString from a property list (PLIST) and adds it to an array. This data is not crucial, and can be recreated without affecting the user experience.
It’s a good habit to take advantage of every situation where you can purge data and recreate it without adversely affecting the user experience. This frees up memory, making low memory warnings less likely.
So how can you improve the code here? Implement didReceiveMemoryWarning and get rid of the data in lollipops as follows:
-(void)didReceiveMemoryWarning { [lollipops removeAllObjects]; [super didReceiveMemoryWarning]; } |
That should be it!
Hooray, you made it through all four crash scenarios! Your app works much better and you learned some important debugging skills along the way. :]
You can download the improved project here.
How did you like demystifying iOS crash logs? I encourage you to continue your learning by trying this out in your own apps – and hopefully solving the crashes and making your apps more robust!
If you have any questions or comments on this tutorial or crash logs in general, please join the forum discussion below!
This is a blog post by Soheil Moayedi Azarpour, an independent iOS developer. You can also find him on Google+.
Demystifying iOS Application Crash Logs
标签:
原文地址:http://www.cnblogs.com/xuejinhui/p/4441530.html