Understanding crashlytics UIKit errors
Asked Answered
C

2

8

I am having trouble understanding the UIKit crash reports that I am receiving:

Is there a way of finding out what line of code caused this:

Crashed: com.apple.main-thread
0  UIKit                          0x195694264 __56-[UIPresentationController runTransitionForCurrentState]_block_invoke + 444
1  UIKit                          0x1955d0950 _runAfterCACommitDeferredBlocks + 292
2  UIKit                          0x1955c29ec _cleanUpAfterCAFlushAndRunDeferredBlocks + 528
3  UIKit                          0x195336648 _afterCACommitHandler + 132
4  CoreFoundation                 0x18f1c09a8 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
5  CoreFoundation                 0x18f1be630 __CFRunLoopDoObservers + 372
6  CoreFoundation                 0x18f1bea7c __CFRunLoopRun + 956
7  CoreFoundation                 0x18f0eeda4 CFRunLoopRunSpecific + 424
8  GraphicsServices               0x190b58074 GSEventRunModal + 100
9  UIKit                          0x1953a9058 UIApplicationMain + 208
10 FlexConnect                    0x1001b48c8 main (AppDelegate.swift:20)
11 libdyld.dylib                  0x18e0fd59c start + 4

The error itself is:

Crashed: com.apple.main-thread

EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000000000010

EDIT:

Based of the answer below, I'm wondering if it is advisable to use:

func topMostController() -> UIViewController {
    var topController: UIViewController = UIApplication.sharedApplication().keyWindow!.rootViewController!
    while (topController.presentedViewController != nil) {
        topController = topController.presentedViewController!
    }
    return topController
}

and always call

let topVC = topMostController().dismiss(animated: true, completion: nil)

everywhere in my app where I currently have self.dismiss(animated: true, completion: nil)?

Is this a necessary check or how can I pin down where self.dismiss is having an issue?

Some sample dismissals:

@IBAction func returnToDash(_ sender: UIButton) {
     self.dismiss(animated: true, completion: nil)
}

let pending = UIAlertController(title: "\n\n\n\(title)", message: nil, preferredStyle: .alert)
displayActivityAlertWithCompletion2(ViewController: self, pending: pending){_ in
   Helper_StatusCheck.doSync(_cleanSync: false){
        Prefs.is_Syncing = false
        DispatchQueue.main.async {
            pending.dismiss(animated: true){
                   Toast(text: "Upload sync completed").show()
                   self.dismiss(animated: true, completion: nil)
            }
        }
   }
}

where displayActivityAlertWithCompletion2 looks like:

public func displayActivityAlertWithCompletion2(ViewController: UIViewController, pending: UIAlertController, completionHandler: @escaping ()->())
{
    //let pending = UIAlertController(title: "\n\n\n"+title, message: nil, preferredStyle: .alert)
    //create an activity indicator
    let indicator = UIActivityIndicatorView(frame: pending.view.bounds)
    indicator.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    indicator.color = UIColor(rgba: Palette.loadingColour)
    //add the activity indicator as a subview of the alert controller's view
    pending.view.addSubview(indicator)
    indicator.isUserInteractionEnabled = false
    // required otherwise if there buttons in the UIAlertController you will not be able to press them
    indicator.startAnimating()



    ViewController.present(pending, animated: true, completion: completionHandler)
}

EDIT 2 :

Some sample popover methods in my app:

 @IBAction func search(_ sender: UIButton) {
        if let popView = UIStoryboard(name: "AssetCommon", bundle: nil).instantiateViewController(withIdentifier: "searchPop") as? searchPopVC {
            popView.delegate = self
            popView.modalPresentationStyle = .popover;
            popView.popoverPresentationController?.delegate = self
            popView.popoverPresentationController?.barButtonItem = searchButton
            popView.popoverPresentationController?.permittedArrowDirections = .any
            popView.preferredContentSize = CGSize(width: 300, height: 70)
            self.present(popView, animated: true, completion: nil)
        }
    }

and search pop:

class searchPopVC: UIViewController
{
    @IBOutlet weak var searchBar: UISearchBar!

    weak var delegate: SearchPopDelegate?

    override func viewDidLoad()
    {
        super.viewDidLoad()

        searchBar.delegate = self;
    }

    override func didReceiveMemoryWarning()
    {
        super.didReceiveMemoryWarning()
    }

    @IBAction func performSearch(_ sender: UIButton)
    {
        let term = searchBar.text ?? "";
        delegate?.performSearch(with: term)
        self.dismiss(animated: true, completion: nil);
    }
}

extension searchPopVC: UISearchBarDelegate
{
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        let term = searchBar.text ?? "";
        delegate?.performSearch(with: term);
        self.dismiss(animated: true, completion: nil)
    }
}
Choking answered 10/10, 2017 at 9:46 Comment(0)
E
3

You won't be able to find the line of code from this crash. However, this crash happens when the view controller you use to call dismiss(animated:completion:) is removed from the view hierarchy prior to the animation completing.

Depending on how your code is setup, you can try to request a view controller higher up to call the dismissal. Another solution could be to retain the view controller in a property until your for sure done with it.

Edit: 1/5/18

In response to the comments, here is an example of how a function can be made for view controllers that logs an event and dismisses.

extension UIViewController {
    func dismissAndLog(animated: Bool, completion: (() -> ())? = nil) {
        // Here are two examples of how your view controller can be identified.
//        let id = title
        let id = String(describing: type(of: self))
        CLSLogv("Dismissed View Controller: %@", getVaList([id]))

        dismiss(animated: animated, completion: completion)
    }
}

I'm not entirely familiar with Crashlytics or their API, so if this logging is giving you issues, you can check out these links.

Also I don't know how they provide the data to you, so I can't explain to you the best way to parse it. However certainly the timestamps can be successfully used as a final solution. You can also try emailing their support asking for the best way to map these logs to the crash.

Eloyelreath answered 26/4, 2018 at 5:38 Comment(12)
do you know where an issue like this is likely to occur? If a button press called self.dismiss and the user mashed it, it may be called twice? Would it be overkill to disable buttons that call self.dismiss, on press? I don't think that example is where my issue is anyway as the crash is happening too frequently and not so many users would be button mashingChoking
is there any logging i could do with crashlytics that would help me figure out which viewController the issue is happening on? I have about 200 VCs :/Choking
do you have any subclasses of UIPresentationController? i would look to see how you're dismissing those view controllersEloyelreath
no subclasses of UIPresenentationController. Generally all dismissals are basic self.dismiss(animated: true, completion: nil) sometimes they are within a completion handler of an activity alert dialog. I have some popover controllers too. Should popviewcontroller and present view controller also be considered or is this issue solely linked with dismiss?Choking
its been a while since i used a popover controller, and with it being deprecated i'll assume your talking about the UIPopoverPresentationController. if so, this is using the presentation controller and would be a good starting point to debug. also in your updated code, in the while it says, topController.presentedViewController!. this is a guaranteed way to create a crash in your app. stay away from optional bangs (!) at all costs.Eloyelreath
I haven't implemented that topcontroller approach yet. I just saw the code and was asking if it was advisable, since somehow self.dismiss is leading to so many crashes in my app. There is a check in that snippet saying if it isn't = nil before the forceful unwrap. I'll add some code used surrounding a popoverPresentationController dismissal and see if you find anything suspect, thanks!Choking
if you're able to reproduce this crash, then turn on exception breakpoint and this should help you find the line of code your crashing at.Eloyelreath
unfortunately I cannot reproduce this crash at all. I've been building upon this app for over a year testing and building 5 days a week and never have I experienced it, yet I am seeing this is happening often through CrashlyticsChoking
being that you have so many vc's it'll be impossible for me to help you without seeing all the code. you might have to rely on brute debugging. you can Crashlytics.log before all present and dismiss calls. in crashlytics you should then have a more clear path of which vc was called before the crash. that should help narrow this down. possibly you can make a convenience function the does the dismiss and the log together to help keep your code more manageable.Eloyelreath
so the issue could also be linked with present also? What about push and pop?Choking
im fairly confident this has to do with only dismiss but to be safe, and for better logs, you should also include present. you dont need to worry about push or pop for this error.Eloyelreath
how might a convenience function like you have suggested look? I'm wondering how best to add this in. Would I be subclassing UIViewController? Could you update your answer with some code? and how would I then be able to analyse the logs to link with the crash? by timestamp of event and timestamp of the crash? Thank you for your help. With these last few questions answered, I should have enough info to accept this answerChoking
M
0

var topController: UIViewController = UIApplication.sharedApplication().keyWindow!.rootViewController!

This many exclamation marks should tell you that there is something not great about this code. Guard and check these assumptions because any one of them may blow up.

Other than that, what gives you some really weird errors, is if you do these kind of dismiss/pop/present actions as part of a defered block like you are doing in Helper_StatusCheck.doSync. There can be all kinds of changes to the navigation stack in between and your assumptions may not hold.

Mccubbin answered 30/4, 2018 at 11:43 Comment(4)
What sort of changes to the navigation stack may have happened? I do this a lot so I am wondering, how to be able to call a dismiss on self after a method returns a completionHandler?Choking
I haven't actually implemented the topController code which I asked about in the question, so my crashes are not coming from there. That part was just asking for advice on whether something like that should be necessaryChoking
I have an app where the user could do navigation actions (next, back, dismiss) while waiting for the callback to complete. If things have changed and your callback fires it still tries to do stuff in self (which is likely a viewcontroller) but it may no longer be part of a navigation stack self.navigationController is null. So that is stuff you can check before doing the action. Though in the case of dismiss that is I think a no-op in those cases.Mccubbin
in a lot of my cases I have a splitViewController which is presented modally. And on completion of a method, I dismiss in the callback. I wouldn't always have a navigationController to check. In cases where I do have a navigationController, I would usually uses self.navigationController?.popViewController(animated: true, completion: nil). I wouldn't be forcefully unwrapping a navController. Is this what you thought I might have done? With code self.dismiss(animated: true, completion: nil), what check would I need to do around this? Thanks for the responseChoking

© 2022 - 2024 — McMap. All rights reserved.