dimystifying iOS Application Crash Logs

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!

What Is A Crash Log, and Where Do I Get One?

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:

Devices tab in Xcode Organizer

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. 

Crash Reports in iTunes Connect

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:

Crash Reports in iTunes Connect

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.

What Generates a Crash Log?

There are two main situations that can result in a crash log: 

  1. Your app violates OS policies.
  2. There are bugs in your app.

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:

  • application:didFinishLaunchingWithOptions:
  • applicationWillResignActive:
  • applicationDidEnterBackground:
  • applicationWillEnterForeground:
  • applicationDidBecomeActive:
  • applicationWillTerminate:

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 and NSOperations 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 a UIApplicationDidReceiveMemoryWarningNotification 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! 

A Sample Crash Log

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. 

  • Incident Identifier is a unique identifier for the crash report.
  • CrashReporter Key is also a unique key that is mapped to the device identifier. Although it is anonymized, it gives you a very useful piece of information: if you see 100 crash logs from the same CrashReporter Key or a few CrashReporter Keys, it means that the issue might not be a widespread problem, limited to only one or a few devices. 
  • The Hardware Model identifies the device type. If you get a lot of crashes from the same device model, it might mean that your app is not working properly on a specific model. In the log above, the device is an iPhone 4s. 
  • Process is the name of the application. The number in the brackets is the process ID of the application at the time of crash. 
  • The next few lines should be self-explanatory.

(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: 

  1. The frame number – in this case, 2.
  2. The name of the binary – in this case, XYZLib.
  3. The address of the function that was called – in this case, 0x34648e88.
  4. The fourth column is divided into two sub-columns, a base address and an offset. Here it is 0x83000 + 8740, where the first number points to the file, and the second points to the line of code in that file.

(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.

Demystification with Symbolication

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 Archives tab. 

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.

Low Memory Crashes

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 the Automatic Snapshotting flag or press the Snapshot Now button manually. 

You will investigate a low memory crash log later on in this tutorial.

Exception Codes

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:

  • 0x8badf00d: Reads as “ate bad food”! (If you squint your eyes and replace the digits with alphabetic characters. :p) This code indicates that an application was terminated by iOS because a watchdog timeout occurred. Basically, the application took too long to launch, terminate, or respond to system events.

  • 0xbad22222: This code indicates that a VoIP application was terminated by iOS because it resumed too frequently.
  • 0xdead10cc: Read this one as “dead lock”! It indicates that an application was terminated by iOS because it held onto a system resource, like the address book database, while running in the background.
  • 0xdeadfa11: Another cute code – read it as “dead fall”! It indicates that an application was force-quit by the user. Per Apple, force quits happen when the user holds down the On/Off button until “slide to power off” appears, then holds down the Home button. As per Apple Documentation, a force quit event results in a 0xdeadfa11 exception code, presumably because the app became unresponsive.

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.

Swimming Time!

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:

  1. Download the code and open the project file in Xcode.
  2. Connect an authorized iOS device with the correct provisioning profile.
  3. Select the iOS device as the target from the Xcode toolbar – not the Simulator – and build the app.
  4. As soon as you see the default screen (the full-screen image of the app) on the device, hit the stop button in Xcode.
  5. Close Xcode.
  6. Open the app directly on the device.
  7. Test the scenarios, and after you are finished, connect the device and get the crash logs via Xcode.

Scenario 1: Bad Code for Breakfast

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?! In application:didFinishLaunchingWithOptions:?!! Who wrote this code?!

Network calls on the main thread makes kittens sad.

Network calls on the main thread makes kittens sad.

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];
         }];

Scenario 2: Button FUBAR

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:

2    CoreFoundation    0x36c02e02 -[NSObject(NSObject) doesNotRecognizeSelector:] + 166

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. :]

Scenario 3: Another Bug On the Table

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 in RMBookmarksViewController.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!!!!!

Scenario 4: Leggo Those Licks!

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: 

  • Free pages refers to available memory. Each page is approximately 4KB, so in the log above, available memory is about 3,872 KB (or 3.9 MB).
  • Purgeable pages are those parts of memory that can be purged and reused. In the log above, it is 0KB.
  • Largest process refers to the application that was using most of the memory at the time of the crash, which in this case is your app!
  • Processes gives you a list of processes, along with their memory usage at the time of the crash. You are given the name of the process (first column), the unique identifier of the process (second column), and the number of pages being used by the process (third column). In the last column, State, you can see the state of each app. Usually, the app that caused the crash is the app with the frontmost state. Here it is Rage Masters, which was using 28591 pages (or 114.364 MB) – that’s a lot of memory!

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! 

Where to Go From Here?

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!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值