We developers are not perfect humans, we make mistakes from time to time. Crashes are one of the most common problems we encounter because of those mistakes.
Crash reporters have been an iOS developer's long time friend. They make it easy to find out where things go wrong like the image below.
You know immediately that the crash happened in ViewController.btnDidTap
. All you have to do then is to try reproducing the problem in that method.
In other cases though, crashes can be hard to debug, like this one
This happened when we did something wrong with UICollectionView. This one can tough to debug if we have dozens of view controllers that use UICollectionView. It's not obvious which view controller that caused the crash.
What should you do in this situation?
Add more debugging information.
A lot of people know how to use Crashlytics, but not many know how to improve the debugging information.
Here are some less well known features of Crashlytics that I'm sure will be useful for you.
Note
In this post I'm going to use Firebase Crashlytics as an example because it is the most popular one. Other crash reporters also have similar features so you can still benefit from the techniques I will describe.
1. Filtering by user id
Imagine how easy it would be if you can just limit the search of your crashes by user id. This is what Crashlytics has already provided.
Adding user id is only one line of code away. Add this code below whenever your user has logged in or logged out from your app.
// Call this when the user is logged in
Crashlytics.crashlytics().setUserID(“12345”)
// Call this when the user is logged out
Crashlytics.crashlytics().setUserID(“”)
Once you added this code, use the search bar to filter crashes made by this user.
2. Adding custom value
Crashlytics has already provided us a lot of useful information such as the iPhone type, OS version, device orientation and so on.
This is not enough in some cases, and fortunately adding more information also takes only one line of code.
crashlytics.setCustomValue("My value", forKey: "My key")
Once you're done, it will show up like below.
Here are some ideas of custom values you should put in Crashlytics:
- Calendar type
- Language
- Region
- Time Zone
- Accessibility settings
This is how the information going to look like
Case study: A complaint from year 2560
A few years ago we received a crash report in a certain view controller. We know where it happened but not what triggered it.
After looking at the custom values, we noticed a pattern. it is immediately clear that all the crashes happened only when our users use the Buddhist calendar in their phone.
3. Logs/Breadcrumbs
Another tool that I find useful is the logging capability. It shows events in order of time. This improves debugging speed because we know the steps to reproduce it.
Where should you add the logs?
App life cycle
Some crashes happen not when the user is using the app, but when the app is in background. By logging every life cycle method, you can know for certain when the crash happened.
Here are some of the app life cycle methods you should log
application(didFinishLaunchingWithOptions:)
applicationWillResignActive
applicationDidEnterBackground
applicationWillEnterForeground
applicationDidBecomeActive
applicationWillTerminate
Using the above image as an example, you can clearly tell that the crash happened when the app entered the background state.
App entry points
The iOS app ecosystem is getting more complex each year. There are a few ways to launch the app besides tapping the icon from the home screen:
- Universal linking
- Push notification
- Home screen quick action or 3d touch
- Siri Shortcut
- WidgetKit
- And many other ways
Here is the log when the crash happened when the user is in another app:
Case study: Can't notify the dead
There was a crash that happened in a very specific condition:
- The app must be in a killed state
- The crash must be triggered only with a specific push notification payload.
By logging the app life cycle and app entry points, we can see how to reproduce this specific scenario. This would be a chore to find without the help of logging.
Opening view controllers
Let's go back a bit to the UICollectionView crash I mentioned before.
If we have a list of previously visited view controllers, finding out where the crash happened would be easy.
One straightforward way to add the log is by using this code
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Crashlytics.crashlytics.log("\(self)")
}
And here is the result. You can tell immediately where the crash happened.
But I have 100 view controllers 😰
Adding the code snippet above for 5 view controllers is not a problem, but it can be tedious when you have 100 view controllers.
Fortunately there's a solution, you can use swizzling to log all the view controllers.
For those who never heard of swizzling, it is a way to swap a method implementation with ours. You can find more about it by searching the internet.
This code snippet below replaces every UIViewController's viewDidAppear
method that is implemented by Apple with our implementation.
Note that I'm using a library called JRSwizzle
in this example to make swizzling quick simple.
extension UIViewController {
// This method that will replace Apple's viewDidAppear implementation
@objc public func swizzled_viewDidAppear(_ animated: Bool) {
Crashlytics.crashlytics().log("\(self)")
// call original viewDidAppear
self.swizzled_viewDidAppear(animated)
}
}
// Do the swizzling at the start of the app
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
FirebaseApp.configure()
// From now on, swizzled_viewDidAppear will be called time a view controller appears on screen
try? UIViewController.jr_swizzleMethod(
method: #selector(UIViewController.viewDidAppear(_:)),
withMethod: #selector(UIViewController.swizzled_viewDidAppear(_:))
)
}
Now Crashlytics will automatically log every view controller that is visited by the app 🥳.
Conclusion
Debugging crashes can be difficult sometimes, but crash reporters have some tools that you can use to make them easier.
If you have been scratching your head figuring how to reproduce this crash, try the techniques described above. I hope it can solve your problems!
Top comments (0)