Developer Tools • iOS • 40:34
Even the best apps crash sometimes. Diagnosing and fixing your app's crashes can make the difference between improving it or losing your customers. Learn the skills you need to identify and fix your crashes and avoid common pitfalls.
Speakers: Madhuwanti Vaidya, Bill Dirks
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
[Madhuwanti Vaidya]
Hello and welcome everybody. And we're here to help you understand crash reports on the iPhone OS. As a short introduction, we welcome the iPhone Data Analysis team, and we're are responsible to collect, process, and analyze customer crash logs. So when a customer is using your device-- using our device, and they see a crash, and then they agree to send this diagnostic information to Apple, we are responsible for collection the crash logs and analyzing them. And in this process, we've learned a lot about crash logs, and we want to share this information with you.
Let me begin by saying that nobody or no customer likes an app that crashes. Let's look at some customer reviews on the iTunes Store. Here's one that says that this app would have been 4 or 5 stars, but it's not because it's very unstable. Here's another one that says this is the only application which is unstable on the iPhone. Here's one that says keep your buggy garbage. Here's another one, and another one, another one, and another one that says the app will not launch.
So you, as app developers, will dread to see such kind of reviews for your application on the store, because we know that one bad review leads to another bad review which leads to lower sales for your application. So more customer-- more crashes leads to bad customer reviews lead to lower sales.
And we're to help to help you kind of avoid these situations, so they look more like this. Which is fewer crashers, better customer reviews, and higher sales for your application. As a short outline of what I'm going to be talking about today, first, I'll tell you about what crash reports are and how they look.
Then I'll talk about the different types of crash reports we see on the iPhone OS. Then I'll tell you how to get this crash report from your device, from your friends' devices and customer devices onto your host machines. Sorry. Next, I'll tell you about how to symbolicate these crash reports so that you can understand them better. And finally, I'll talk about the common crashes that we've see when we've analyzed customer crash logs.
So what are crash reports? How many here have actually seen a crash report? Wow, that's a fair number of people. So I'm not the only one who writes code with bugs in it. So what happens? So when an application crashes, the iPhone OS writes this crash report which contains some diagnostic information that will help you debug your application, rather than trying to make a guess as to what could have gone wrong. It will find you to the exact line in your code that could have caused the crash.
Let's look at an example crash report. This is what a crash report looks like. I'll go through this section by section, and within each section, line by line. Let's look at the first section of a crash report. This will give you some basic information about the crash. The first line is the Incident Identifier. This is unique for each crash report. The next line is the CrashReporter Key. This uniquely identifies a device.
But remember that when we see this, we cannot exactly pinpoint, oh, this came from your device. It's anonymized. The way we use this information is that if we see a thousand crashes all coming from a single CrashReporter Key, then we know that this problem is not really widespread, and it's contained to this single device. So the way you can use this information is normalize your data.
If you see a hundred crash reports from customers all coming from only one or two CrashReporter Keys, which is one or two devices, you can kind of make a guess that it's not as widespread. It's only limited to these two devices. Whereas, if it came from hundred different CrashReporter Keys, then you know you crash is like, affecting a hundred different users.
Let's go to the next line which is the Hardware Model. This can be any of the iPhones, the iPod touches or the iPad. Again, the way to use this information is if you see all your crashes coming only from the iPad, then you'll know it narrows down your search space to iPad specific code.
You can look at only that code and see what could have caused the crash only on iPads. In this case, it's iPod 3,1. The next line will tell you the Process, which in your case will be the name of your application. And the number in square brackets is the process ID of the process at the time of the crash.
The next four to five lines, you don't have to worry about as much because they'll be consistent across crash reports. Let's look at the next section of the crash report. This gives you some basic information about the crash. It will tell you the Date and the Time of the crash, and it will tell you the OS Version that the device was running at the time of the crash.
So the way to use this information is if you'll see a lot of crashes coming from iPhone 3.1.2, and none from 3.1.1, you can make a guess as to there could have been some change in the API or the way the code-- your code interacts with these new API's, which is causing this crash.
Let's look at the next section which is the most important section of a crash report. This is the Exception section of the crash report. It will tell you the Exception Type, which in this case is EXC_BAD_ACCESS. This means that your program or your application was trying to access memory, which wasn't available-- sorry-- available to it at that time.
It will tell you the Exception Code and it will tell you the Crashed Thread, which in this case, is thread number 6. The iPhone OS is a multithreaded platform. So there is more than one thread running at a given time. So in this case, if you see that the crash thread is thread number 6, you look at the backtrace of that thread. The backtrace is in the next section. So you concentrate on the backtrace of the crash thread and debug your application using Thread Backtrace, and concentrate only on the crash thread section. So let's actually go to thread number 6 and look at its backtrace.
What is a backtrace? A backtrace lists all the active frames at the point of the crash. So it will give you a necessary list of function calls that we'll meet when this crash occurred. It is divided into four columns. The first column will give you the frame number. The second column will give the name of the binary, in this case, its Foundation. The third column will give you the address of the function that was called.
The fourth column divides this address into a base and an offset. Later on in the talk, I'll tell you how to convert these addresses into symbol names which are essentially file names and line numbers, so that they point exactly to the line in your code which could have caused this crash. Let's go to the next section.
This section will give the values and the registers at the time of the crash. Don't worry about the section too much, because in most cases, the backtrace gives you all the information you need to debug your application. Finally, the last section which just lists out all the binaries that were loaded at the time of the crash.
So I showed you an example crash report. Let's talk about the different types of crashes that we see on iPhone OS. We make sure that we have a high quality of user experience for the user when they interact with our device. To do this, we need to ensure certain policies. And these policies can terminate your applications if your application doesn't respect them. And because of this termination, the crash report is written out.
The first kind of crash report is a Watchdog timeout crash report. This happens when your application fails to launch, resume, suspend, or quit in a given timeframe. So if the user is interacting with your device, and the application doesn't launch within a given timeframe, the application will be terminated and a crash report is written out.
The second type of crash reports we see are User force-quite. A user will force-quit your application if your application is not responsive enough, and this will cause the crash report to be written out. So the third type of crash reports we see are the Low Memory termination crash reports. The iPhone OS is not exactly like the Desktop OS, because it has only a limited amount of memory to play with.
And if the OS realizes that it doesn't have enough memory to continue with interaction with the user, it will send out this low memory notification to applications. If your application respects this low memory notifications, and it freeze up memory, and the OS realizes that it's gotten enough memory to continue running smoothly, things will run fine. But if there isn't enough memory and if the application doesn't free up memory, then your application will be terminated, and a low memory log will be written out.
Finally, the fourth type of logs that we see are Application crashes due to bugs in the code. Let's look at each of these types of crash logs. Here is an example of a Watchdog timeout crash log. Remember, I said that the exception section is the most important section of a crash log. So if you concentrate on this section, in this case, you will see that the Exception Code is 0x8badf00d.
If you see this as an exception code, you should immediately realize that this is a crash log from a Watchdog timeout. The next section will tell you what your application was trying to do at the time. In this case, it says that your application failed to resume in time.
And the number one cause of these crash supports that we seen is when your application is trying to make a synchronous request to a URL to get data. There are API's to do asynchronous request, and try to use those. And don't block the main thread for more time than necessary. Bill will actually show you a demo of a how-- of a crash report, or a Watchdog crash report.
[Bill Dirks]
So one thing San Francisco is famous for is having earthquakes. So for our demo today, we've decided to use an app called SeismicXML which isn't-- which I downloaded from the developer website, and so can you, and it's an earthquake themed app. What this app does is it makes a URL request to the United States Geological Survey website, pulls down data about earthquakes, and then displays it to the user in a table view. So I'm just going to show you the app working properly at first.
[ Pause ]
So I'm just going to launch the app, and if I can-- the network connection works, then we'll see this table view being populated with earthquake data. So if it doesn't work, then we'll see a Watchdog timeout right now. So--
[ Laughter ]
We're just going to give it one more second, and then-- so that basically, this is exactly why you don't want to be calling synchronous requests on the main thread, because like right now, at least you see the title bar and the table view is responsive, even though there's nothing in it. But if you were doing this in your application, you're calling on the main thread that blocks all the user interaction with your application.
So your not going to-- so the user's going to launch your app, and then they're not going to be having a very good time, because they like, "Oh, what's going on here? It's not doing anything." And here, they can just blame the network. But if your app just totally-- and this was not on the main thread. So it didn't Watchdog.
But I have a version that will Watchdog. Because SeismicXML is written to call-- to ask for the URL asynchronously, which is good. So therefore, the app didn't die, and it gave me a meaningful warning message. But like, Siesmicdog app, which is a version of the SeismicXML app, is one where I'm just-- I changed the code. So I'm just requesting the URL in the main thread.
And so, I'm going to switch to-- this is just going to hang here for a while like it did before. But in this case, it's just going to die. So the user would not-- you don't see title bar, you can't interact with the page, and it just dies.
So now that we've seen what a crash looks like, I'm going to show you how to debug that crash and get information using Xcode. If you have such a crash on your device, you can debug it immediately by plugging your device into your host, and then opening up Xcode. From Xcode, you can open up the organizer which is in the window pull down menu. And-- alright.
So once you open up the organizer, you can click on your device, which is on the left side here, and then click on the device logs tab. Clicking on the device logs tab will make Xcode pull your device and pull down any crash logs that are on the device.
[ Pause ]
The Seismicdog app that we just generated at 9:14. So as Madhu mentioned, the first thing you-- one of the most important sections of a crash report is the exception section. So we'll go there first. And here, we are at the exception section.
We see these 0x8badf00d exception code, and this means that the Watchdog timeout came in to play. So the Watchdog timeout comes in to play when you have failed to launch, resume, suspend, or quit an app in time, and this is to ensure a good user experience. So the two other things to notice here are-- the highlighted thread is 0, which is the main thread, and that if you look at the applications specific section immediately below, it's says Seismicdog failed to launch in time. So this already gives us a clue on what went wrong with our app and where we should go look to debug it. So now that we see highlighted thread is 0, we can go and look at that backtrace.
So how you read a backtrace is you can start at the bottom, or it's-- in this case, it says start, and that's the application starting up, and then you see it called the main UIApplication. So you just read from the bottom up and that's the recursive list of function calls. Eventually, you'll see calls made by your application.
And the one you want to look at is the highest one in the stack trace, which in this is stacks-- stack frame 3, we see it's from Seismicdog, and then we're going to look at the symbol information. So we know that application died, in applicationDidFinishLaunching in the file SeismicXMLAppDelegate.m line 133.
So from there, it gives you actual information where you can go look at the crash log and then, "Oh, that's where the bug is in my code." So we can look there now. You can also look higher up in the stack frame stack to see what's going on. In this case, there's-- I actually had it sleep in that function because you can't control the network connection, so I want to make sure that this one would die. So now, I'm going to open up our code in Xcode and go to line 133.
And here, I have a # define that says make synchronous URL request. And I just want to remind everyone that as Madhu said, the number one cause of Watchdog timeout is making a synchronous URL request on the main thread. And we have appropriate API's that you can call to prevent this from ever happening to your app. So I encourage you to use them. Thank you.
Madhuwanti Vaidya,: So just a summary. If you see this exception code 0x8badf00d, immediately know that it's a Watchdog timeout.
When this happens, try not to block the main thread, perform task which take longer than required on a background thread, and make-- and use these API's for asynchronous URL request. There were these two sessions, Network Apps for iPhone OS part one and part two, on Wednesday. I would recommend that you go back and watch these videos. They talk more in detail about how to use these API's for asynchronous URL request. Alright. Let's look at the next section-- next type of crash report which are user force-quit crash reports.
Again, concentrate on the exception section. In this case, it's 0xdeadfall, and it tells you that highlighted thread. So if you look at the highlighted thread, you will find out what your application was trying to do which caused the user to force-quit your application. In most cases, your application become unresponsive which is why the user will use this force-quit action, and this crash log will be written out. Let's look at the third type of crash logs which are low Mmemory crash logs.
These look a little different from the other types that we've seen because they need to tell you more information about the memory situation of the device at the time of the crash. So let's look at this section by section. The first section is similar. It will tell you the Incident Identifier, the CrashReporter Key, the Hardware Model, the OS Version, and the Date and time of the crash.
The next section is more specific to low memory logs. It will tell you how many free pages there were at the time of the crash. One page is 4 kilobytes. So in this case there were 581 pages free, which is around 2 MB3. The next line will tell you how many pages were wired, how many of them were purgeable, and what was the largest process in terms of memory. In our case, it's SeismicXML memory, which is the application that Bill is demoing.
The next section will tell you-- will give you a list of all the processes and how much memory each of them was using. It will tell you the name of the process, the unique identifier of the process, and the number of pages that that process was using at the time of the crash.
As you can see here, the first line, which is SeismicXML, was using 38,000 pages, which is a lot of pages. So if you see this type of crashes, what you need to figure out is why your application is using so much memory, and free up memory when required.
This crash log also tells you which processes were jettisoned or terminated, and it tells you which process was active. In this case, SeismicXML was a front-most app, and it was active. So the user was actually interacting with your application, and the application crashed. The iPhone OS, when it reaches its low memory situation, first tries to terminate applications which are in the background. As you can see here, MobileSafari is jettisoned, MobileMusicPlayer is jettisoned. So it will try to free up memory and terminate applications which are in the background. If it still does not get enough memory, it will terminate the front-most application, which is active.
You can see that third last line, which is SpringBoard, is also active. These are the processes that interacts with the user, and it will always be active in these reports. So don't get confused when you see that as the after process. Bill will show you a demo of a low memory log.
[Bill Dirks]
Hi again. So since we don't have good-- I don't have a good network connection here, I'm not going to show you the app crashing, but it looks exactly like it did before, comes up and then it dies. But the really interesting thing is to look at the crash log itself, and see how we can use that to help debug our applications.
So once again, if you get-- if you're using any device and it crashes, or you-- or you've got some crash logs somehow, but let's say your device has crashed, you plug it into your host and then open up Xcode, and once again, go to the window pull down and open up the organizer. If you don't see your crash log immediately pop up here, you might need to, as I did earlier, toggle between tabs to get Xcode to poll your device and pull down the appropriate crash log.
So in this view, the low memory crash logs say, low memory in type, and they often have more than one application in in the application section. This is because many applications can be jettisoned at one time. So what can you get from looking at a low memory crash log? Unlike the other crash logs, there's not actual information as in oh this is the line number that I need to go look at.
What you need to look at here is, one, you're getting them, and so, that's no good. And-- because the iOS tries really hard not to jettison the front most app-- so the user that's using your app, we try as hard as we can not to jettison it, because that's what the user wants to do.
But sometimes, if the application does use a lot of memory, it will be jettisoned. But before the iOS jettisons the application, it will send a low memory warning. So as Madhu mentioned, if you listen for this notifications and respond by bringing up any available memory that you can, this will prevent-- help prevent your application from being jettisoned. In the-- and then the other thing to notice, which goes along with that is that in this case, our app was the active app, and it was the jettisoned app. That means the user was actively using your app. So that's a situation that you want to avoid.
Other things you can do in terms of-- you can do beforehand to try to prevent these crash logs from happening, is there's some tools and instruments such as the leaks tool and the alloc tool which there's other sessions that Madhu will point out, that you can watch the videos for.
And also, you can use-- you can build and analyze to help you try to find any memory leaks also. So in summary, you basically-- there isn't direct actual information in these logs, but these logs mean that the user is having not a good experience with your application. So you should try to work on fixing this. Thanks.
[Madhuwanti Vaidya]
Just to summarize again, so iOS will send out low memory notification when it realizes that there isn't enough memory to run smoothly, respect these low memory notifications. When you get this notification in your application, release objects that can be reconstructed. Also, release objects that are cached so that the iOS does not have to terminate your application. There are at least two sessions that had been mentioned, Advanced Memory Analysis with Instruments, that was yesterday, and Performance Optimization on iPhone OS, which is again, yesterday. So go back and watch these videos.
Alright. So I talked about the different types of crash reports that you'll see on the iOS. How do go about actually getting this crash reports from your device onto your host machine? First thing is, you want to build and test your application on a device. So-- because the iPhone simulator simulates most aspects of the iOS, but not all of them. So, low memory situation, Watchdog time out, are not simulated by the simulator.
So build and test your application on your device. If you see crashes on your device, as Bill showed you, just sync up your device to your host and open up Xcode, Xcode will automatically pull the crash logs from the device on to your host machine. And if you go back to the organizer window in the device log section, you will see a list of all crashes on your device.
The next stage in building and testing your application is when you build your application, and if you give it to a few of your friends who are willing to test it out for you, what do they do when they see crashes on their devices. Again, they sync up their device to their host machine, and depending on the OS that their host is running, the crash logs will be copied to these three different-- to this location. They need to go to this location and e-mail the crash logs to you.
Finally, when your application is live on the App Store, we also provide you a way of getting customer crash logs. So if you log into your iTunes Connect account, there is this section which is crash reports, if you click on that, it will show you this nice-- not nice-- it'll show you this list of crash report--
[ Laughter ]
So it will show you crashes due to bugs in your application, it'll show you these timeout reports, and these low memory logs. So go look at them, understand them, and try to fix bugs in your application which is causing these crash logs. There is a session which talks more in detail about iTunes Connect-- Publishing with iTunes Connect. It happened yesterday, so go back and watch the videos for that.
The example crash log that I showed had addresses in the thread backtrace. So you need to be able to go from this address information to symbol names, so that we can understand these crash logs better, and then we can actually go and look in the code of a-- in the line of code that caused this crash.
This process is called symbolication, and I'll tell you how to symbolicate these crash logs. So here's a nonsymbolicated-- unsymbolicated crash log. As you can see, there are addresses in the thread backtrace. We don't know what to do with these addresses, since we're not computers. Here's a symbolicated back log-- symbolicated crash log. Let's look at one line.
So this address is actually now converted to this function name with this applicationDidFinishLaunching, and the file name and line number. So how do you do this? How do you go from address to symbol information? Well, Xcode does it for you. So if you drag and drop your crash logs into the Xcode organizer window in the device log section, it'll automatically symbolicate your log for you, so that you can see this file name and line number.
In order to be able to do this, Xcode needs to have access-- or Spotlight needs to have access to the binary and .dSYM for your application. And remember, you need to store your binary and .dSYM for each version of your application that you upload to the App Store. You can not go back and rebuild your application, and hope the crash logs will be symbolicated.
So, Xcode 3.2.2 or later, will actually have this option of Build and Archive which builds your application and stores the binary and .dSYM in a safe location so that you can symbolicate crash logs, because not all crash logs that you get from customers will be symbolicated. And you need to do this for each version of your application. I cannot stress the fact more now.
Finally, let's talk about the most common crashes that we've seen when we've analyzed customer crash logs. These are the three most common crashes that we've seen from customer crash logs. All of them relate to memory management on the iPhone OS. Memory management in-- on Objective-C requires you to understand the Retain/Release Model model that Objective-C works with. And you need to understand this and apply it consistently across all code. The first most common crash that we've is seen is crashes due to over-released objects.
The second is due to null pointer dereferences. And the third is when you try to insert a nil object into an array or a dictionary. If you try to do that, your application will hang or it will crash, and a crash report will be written out. Bill will show you a demo of these types of crashes. I think Bill is the only person on stage who wants that application to crash.
[Bill Dirks]
Thanks, Madhu. Yes, I'm going to walk through crash logs from each of these three types of crashes, so you can look at the backtrace and use that information to help you debug your application. I think crash logs that point to bugs in your code are actually the most compelling type of crash log because that gives you information, and you can use that information to find real bugs in your code that's live in the real world, fix it, and then the user will have a better experience in the future. So first, I'm going to show you an over released object crash log.
So once again, you can open up your organizer window which is in the window pull down, and here it is. And before the session this morning, I launched my app and crashed it in every way I could. So I-- there's a version of the application called SeismicOR, or Seismic Over Release, which has an over release error in it. And let me zoom in.
So once again, when you get a crash log, one of the first sections you want to go look at is the exception section. And here, we see the exception type is EXC_BAD_ACCESS. And what EXC_BAD_ACCESS means is that you're trying to access memory that you don't own. So you already know from looking at that, that this is some type of memory issue. It also shows that the crashed thread is thread number 7. So now, we go look at thread number 7 in the backtrace.
And once again, you can start at the bottom, you see the thread spinning up, but the line that you're most interested in is the line where you see your app name that's highest up in the call stack. And here, we see it was in the SeismicXML app delegate parse earthquake data function, and-- which is in SeismicXMLAppDelegate.m line 234. So right there, we already know where we can start to look for the bug in the-- in this code. And we can look at-- we all look at this code.
[ Pause ]
And here, we see pool release. So, this-- and pool here is our Autorelease pool. So right now, we know that we're releasing some object and we shouldn't be. And so, that-- that's the cause of our bug. In fact, if you looked at the crash log, just point it up-- which I didn't point out earlier, and you look at the line above where your application has crashed. This shows you that there's an NSAutoreleasePool release is the next frame up.
So from the crash log itself, you know that it's some Autorelease error. So this gives you a starting point to look for your bug, and here, since I made the bug, it's pretty easy for me to discover it. But we instantiate this parser object which we added to the AutoreleasePool, and then we used it to parse the data that came from the website, and then we're done with this, so we released it.
But since it was in the pool, it got released again when we did a pool release. So that's the type of process that you'd go through when you get a crash log and they're hunting down bugs in your code. OK. Next, I'm going to show you a crash log from our second most common type of crash, which is dereferencing null.
So this crash log is very similar, and you use the same process to the over release crash. After your app crashes, you can open up the device that log in Xcode and have it symbolicated, you look at the exception section, again, EXC_BAD_ACCESS, so we know it's a memory-related issue. Here, the crashing thread is 0.
[ Pause ]
So we can look at the backtrace of the crashing thread and we find that the highest frame that our app is making-- that appears in this thread is the-- in RootViewController line 76.
Now, the like-- next line up here isn't quite as useful because it's an obviously message send, which is a little more opaque for us as the developer. But we do have the line of code that we can go look for. So I'm going to open up this piece of code also.
[ Pause ]
So this is just a classic dereferencing null error. But-- and your code will be much more complicated than this. I just wanted to show you that it gives me a starting point in this case right here, to go debug my application, and then you can go, I'm dereferencing row height, so what could be the problem there? And then you'd have to look at what-- you thought row height existed, but no longer exists. Or in this case, it never existed because I declared it but never malloced any memory for it, and then I'm using it.
So that's the second most common type of error we see. And now, the third most common type of error, and this one is a little trickier to debug, but the process is very similar to the other two, and that's inserting nil into a collection, such as in array or dictionary. Once again, if your app crashes, open up Xcode, go to the organizer. That's the first thing you do. In here, I made a version called Seismic Insert, which I crashed this morning. So I have the log.
Alright. So the exception type here is this SIGABRT as opposed to the SIGSEGV we saw earlier. So SIGABRT's are a little bit-- of a different type of exception. Because here, what's going on is your application has asked the kernel to kill it, and then the kernel comes back and kills your app. And this happens when something like you thrown an exception and it gets caught, and the kernel comes in and kills your app.
So it says here that the crashing thread is 0, but if we look at thread 0, things look OK. So that's why this type of error is a little bit trickier to debug. Because here, we-- on this thread, we just see Seismic Insert, it's starting, it launches main, the run is being setup, and it looks OK.
But with the SIGABRT, what you have to do is you-- then you have to start looking at the other threads. If you don't see the error, in the thread that it is blamed, you have to start looking at the other threads. And with SIGABRT, you want to look for an abort being called in the thread.
So if we look through all these threads and go to thread 6, we see this line that says abort in it. And this is actually the backtrace of interest. This is the backtrace that will show you where the offending code is. And here, it was-- because of this Objective-C exception being thrown. So that's-- this is a common scenario where the blame thread isn't the correct blame thread, and you have to go looking at other threads. So you want to look for the abort line, and then often it's because of an exception being thrown.
So-- but once you find this thread, there's a lot of things bad in this thread. It says kill, terminate, not good words. You want to do the same thing you did before. Go look for the highest frame, that's your app, in this case-- this app is called Seismic Insert, and look at the line of code, which is in the AppDelegate.m again, line 234.
[ Pause ]
There we go.
[ Pause ]
Alright. So on line 234, we're going to see that I'm adding this new earthquake to the earthquake list. But in fact, it was nil.
And you know, your code of course, again, will be more complicated than this. But this shows you the starting point to look at, and for some reason, some object that has not existed, no longer exists, so then you have to hunt down and figure out why did it go away and how do I go about fixing this? But the crash logs contain information of crashes that are happening in the real world that you can go then use to debug your application and make a better user experience and hopefully, some more apps. Thanks.
[Madhuwanti Vaidya]
So just in short, understand the Retain/Release model and apply it consistently across all your code. There is a session which is right after this, at 11:30, Advanced Objective-C and Garbage Collection, which talks more in detail about this. There was also a session on Wednesday which you can go back and watch videos for. Just to summarize, nobody likes an app that crashes. And most of all, people at the App Store don't like it.
If you got-- if you got a chance to watch the Keynote, Steve mentioned that the third most common reason that apps are rejected from the App Store is because the app crashes. So you need to build and test your application on devices that you want your application to run on. I talked about the different types of crash reports. I told you about how to get these crash reports from your device, from your friend's devices on to your host machine. We also provide you a way of getting customer crash reports through iTunes Connect.
Remember to store the binary and .dSYM for each version of your application. Xcode Build and Archive does this for you. And finally, we showed you the three most common crashes that we've seen from customers. So try to avoid these common mistakes, and hopefully, your application will have a better user experience. Thank you for attending this session. Here are the related sessions. And for more information, please feel free to contact Michael.