Introduction
NOTE: If you want to see what was actually happening, and how I managed to solve this issue, please read this follow up.
Here is a really quick post about properly handling crashes. There was a previous post where I highlight how to reduce the damage of crashing. This time, I’ll highlight how you can go a step further to ensure you get those precious crash reports.
Hell’s Gate
Apple actually tries to make sure that you are not sending data regarding the user, without the consent of the user. That’s why in HockeyApp’s SDK they present the user with a dialog that asks them whether the crash reports should be sent or not.
Now, this “gate” stops the SDK from submitting crashes to the service, until the user hits that “Allow” button. Problem is, my app was crashing on launch for a very small percentage of my users, which meant they didn’t reach a stage where they can even hit the allow button!
I was getting bad reviews, complaints, and just an angry mob running after me .. I had to do something about this Hell’s gate!
Desperation
I was a bit desperate initially, so I went the HockeyApp SDK repo on github, since it is opensource, and decided, hey! I can hack the framework and try to block the main thread, pull the crash data, and submit it to my server. Then, I’ll be able to solve the problem.
Hours of running around the HockeyApp SDK yielded that I should play around with PLCrashReporter and crash signals .. To me, that meant new code I had to write, that might not even be stable enough!
I slammed my laptop in frustration..
Universal Approach
Finally, it clicked in my head that I can go ahead with a universal approach that I can use throughout my apps! What if I just show a temporary view in the beginning with a spinner, just to wait till the user hits “allow”, and the crash is sent?
The users started seeing this screen instead of the app just crashing on launch. Even better, they were able to hit “allow”, and I get my crash report!
Some Codez
Time to see some code!
Let’s make one thing clear:
I am not relying on Storyboard initialization
If you rely on the app entry point being in the storyboard itself, that means your app may crash before even reaching the application did launch method! That is because the storyboard will initialize a few classes itself, before you get your chance to run your code… Now that’s no good, is it?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// 1
let window = UIWindow(frame: UIScreen.mainScreen().bounds)
window.backgroundColor = UIColor.whiteColor()
self.window = window
// 2
let startupClosure: CrashViewController.CompletionCallback = {
KIAppManager.sharedManager().initializeApplicationWithLaunchOptions(launchOptions)
window.rootViewController = RootViewController()
}
// 3
if appManager.applicationCrashedLastSession() {
window.rootViewController = CrashViewController(completionCallback: startupClosure)
}
else {
startupClosure()
}
window.makeKeyAndVisible()
return true
}
Please take note of these important points:
- We initialize the window. We need to show something.
- Then, we can create the normal app launch procedure in a closure, to save duplicate code. That code does not run immediately.
- Finally, we check if the app crashed. Based on that, we either show the crash view, or call the launch procedure and resume normally.
One Last Advice
If you have a large user base, and your app actually matter, don’t use swift. Don’t even think about it. Consider it still in beta. Of course, I won’t just say that and leave, here is why.
I got about three common swift crashes that only happen with a subset of my users. The crashes are vague with a SIGTRAP
signal, and to solve them, all I did was migrate away from swift closures to something else… Especially unowned self
. That one’s a real pain.
Since I have more than 100k active users, a small subset of crashes is still significant! Here are a few swift crashes I got (somehow, they are also linked to FBKVOController):
Exception Type: SIGTRAP
Exception Codes: #0 at 0x1000580fc
Crashed Thread: 0
Application Specific Information:
Selector name found in current argument registers: name
Thread 0 Crashed:
0 Application 0x00000001000580fc Application.HomeHeaderViewController.(viewDidLoad (Application.HomeHeaderViewController) -> () -> ()).(closure #1) (HomeHeaderViewController.swift:0)
1 Application 0x0000000100133df0 -[FBKVOController(ExtendedAPI) observeAndExecute:keyPath:block:] (FBKVOController.m:675)
2 Application 0x0000000100055460 Application.HomeHeaderViewController.viewDidLoad (Application.HomeHeaderViewController)() -> () (HomeHeaderViewController.swift:38)
3 UIKit 0x000000018aca0958 -[UIViewController loadViewIfRequired] + 688
4 UIKit 0x000000018aca0668 -[UIViewController view] + 28
5 Application 0x000000010003117c @objc Application.HomeViewController.headerView.getter : ObjectiveC.UIView (HomeViewController.swift:21)
6 Application 0x000000010006ac34 Application.MainViewController.(loadInitialViewController in _B5C9B7278B2090C19811857E444A7BD0) (Application.MainViewController)() -> () (MainViewController.swift:93)
7 Application 0x000000010006a640 @objc Application.MainViewController.viewDidAppear (Application.MainViewController)(Swift.Bool) -> () (MainViewController.swift:75)
8 UIKit 0x000000018acb796c -[UIViewController _setViewAppearState:isAnimating:] + 588
9 CoreFoundation 0x000000018638fe14 __53-[__NSArrayI enumerateObjectsWithOptions:usingBlock:]_block_invoke + 84
10 CoreFoundation 0x0000000186386774 -[__NSArrayI enumerateObjectsWithOptions:usingBlock:] + 296
11 UIKit 0x000000018acb7ac0 -[UIViewController _setViewAppearState:isAnimating:] + 928
12 UIKit 0x000000018ad22c24 -[UIViewController _executeAfterAppearanceBlock] + 60
13 UIKit 0x000000018ad22b8c _applyBlockToCFArrayCopiedToStack + 352
14 UIKit 0x000000018ac94618 _afterCACommitHandler + 568
15 CoreFoundation 0x000000018644ad98 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 28
16 CoreFoundation 0x0000000186447d24 __CFRunLoopDoObservers + 356
17 CoreFoundation 0x0000000186448104 __CFRunLoopRun + 832
18 CoreFoundation 0x00000001863751f4 CFRunLoopRunSpecific + 392
19 GraphicsServices 0x000000018f79f6fc GSEventRunModal + 164
20 UIKit 0x000000018ad0610c UIApplicationMain + 1484
21 Application 0x0000000100123960 main (main.swift:13)
22 libdyld.dylib 0x0000000197a9aa08 start + 0
Exception Type: SIGSEGV
Exception Codes: SEGV_ACCERR at 0xde92bec8
Crashed Thread: 0
Thread 0 Crashed:
0 libswiftCore.dylib 0x000000010060dd34 0x100494000 + 1547572
1 Application 0x00000001001442bc objectdestroy1 (UIView+LPA.swift:0)
2 libsystem_blocks.dylib 0x0000000193981910 _Block_release + 252
3 Application 0x0000000100144804 objectdestroy12 (UIView+LPA.swift:0)
4 Application 0x0000000100144884 objectdestroy14 (UIView+LPA.swift:0)
5 libsystem_blocks.dylib 0x0000000193981910 _Block_release + 252
6 libsystem_blocks.dylib 0x0000000193981910 _Block_release + 252
7 Application 0x0000000100154ac8 -[_FBKVOInfo .cxx_destruct] (FBKVOController.m:90)
8 libobjc.A.dylib 0x00000001932e6b1c object_cxxDestructFromClass(objc_object*, objc_class*) + 144
9 libobjc.A.dylib 0x00000001932f3f38 objc_destructInstance + 88
10 libobjc.A.dylib 0x00000001932f3f90 object_dispose + 24
11 libobjc.A.dylib 0x0000000193301724 (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 560
12 CoreFoundation 0x0000000182ab8e44 _CFAutoreleasePoolPop + 24
13 Foundation 0x00000001839ba784 -[NSAutoreleasePool release] + 144
14 UIKit 0x00000001873e56e8 -[UICollectionView layoutSubviews] + 268
15 UIKit 0x0000000187385648 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 568
16 QuartzCore 0x0000000186cdd994 -[CALayer layoutSublayers] + 164
17 QuartzCore 0x0000000186cd8564 CA::Layer::layout_if_needed(CA::Transaction*) + 316
18 QuartzCore 0x0000000186cd8408 CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 28
19 QuartzCore 0x0000000186cd7c08 CA::Context::commit_transaction(CA::Transaction*) + 272
20 QuartzCore 0x0000000186cd798c CA::Transaction::commit() + 432
21 QuartzCore 0x0000000186d2d1d8 CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 512
22 IOKit 0x0000000183d258d0 IODispatchCalloutFromCFMessage + 372
23 CoreFoundation 0x0000000182b795e4 __CFMachPortPerform + 176
24 CoreFoundation 0x0000000182b8e200 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 52
25 CoreFoundation 0x0000000182b8e160 __CFRunLoopDoSource1 + 432
26 CoreFoundation 0x0000000182b8c0e0 __CFRunLoopRun + 1636
27 CoreFoundation 0x0000000182ab90a4 CFRunLoopRunSpecific + 392
28 GraphicsServices 0x000000018bc635a4 GSEventRunModal + 164
29 UIKit 0x00000001873ee3c0 UIApplicationMain + 1484
30 Application 0x0000000100146b3c main (main.swift:13)
31 libdyld.dylib 0x0000000193956a08 start + 0