简体   繁体   中英

iOS app crashing in debug mode, working in release mode

There are several questions asking the exact opposite of this, and I don't understand how/why running my app in release mode works, but crashes with an EXC_BAD_ACCESS error in debug mode.

The method that crashes is recursive, and extremely!! substantial; as long as there aren't too many recursions it works fine in both debug (fewer than ~1000 on iPhone XS, unlimited on simulator) and release mode (unlimited?).

I'm at a loss as to where begin finding out how to debug the debug mode and I'm wondering if there is some kind of recursion soft-limit bundled due to the stack trace or some other unknown? Could it even be down to the cable as I'm able to successfully run in the simulator without problems?

I should note that Xcode reports crashes at seemingly random spots, such as property getters that I know are instantiated and valid; in case that helps.

I'm going to go refactor it down into smaller chunks but thought I would post here in case anybody had any ideas about what might be causing this.

See: https://gist.github.com/ThomasHaz/3aa89cc9b7bda6d98618449c9d6ea1e1

You're running out of stack memory.

Consider this very simple recursive function to add up integers between 1 and n :

func sum(to n: Int) -> Int {
    guard n > 0 else { return 0 }
    return n + sum(to: n - 1)
}

You'll find that if you try, for example, summing the numbers between 1 and 100,000, the app will crash in both release and debug builds, but will simply crash sooner on debug builds. I suspect there is just more diagnostic information pushed on the stack in debug builds, causing it to run out of space in the stack sooner. In release builds of the above, the stack pointer advanced by 0x20 bytes each recursive call, whereas a debug builds advanced by 0x80 bytes each time. And if you're doing anything material in your recursive function, these increments may be larger and the crash may occur with even fewer recursive calls. But the stack size on my device (iPhone Xs Max) and on my simulator ( Thread.current.stackSize ) is 524,288 bytes, and that corresponds to the amount by which the stack pointer is advancing and the max number of recursive calls I'm able to achieve. If your device is crashing sooner than the simulator, perhaps your device has less RAM and therefore has allotted a smaller stackSize .

Bottom line, you might want to refactor your algorithm to a non-recursive one if you want to enjoy fast performance but don't want to incur the memory overhead of a huge call stack. As an aside, the non-recursive rendition of the above was an order of magnitude faster than the recursive rendition.

Alternatively, you can dispatch your recursive calls asynchronously, which eliminates the stack size issues, but introduces GCD overhead. An asynchronous rendition of the above was two to three orders of magnitude slower than the simple recursive rendition and, obviously, yet another order of magnitude slower than the iterative rendition.

Admittedly, my simple sum method is so trivial that the overhead of the recursive calls starts to represent a significant portion of the overall computation time, and given that your routine would appear to be more complicated, I suspect the difference will be less stark. Nonetheless, if you want to avoid running out of stack space, I'd simply suggest pursuing a non-recursive rendition.


I'd refer you to the following WWDC videos:

  • WWDC 2012 iOS App Performance: Memory acknowledges the different types of memory, including stack memory (but doesn't go into the latter in any great detail);
  • WWDC 2018 iOS Memory Deep Dive is a slightly more contemporary version of the above video; and
  • WWDC 2015 Profiling in Depth touches upon tail-recursion optimization.

It's worth noting that deeply recursive routines don't always have to consume a large stack. Notably, sometimes we can employ tail-recursion where our recursive call is the very last call that is made. Eg my snippet above does not employ a tail call because it's adding n to the value returned by recursive call. But we can refactor it to pass the running total, thereby ensuring that the recursive call is a true “tail call”:

func sum(to n: Int, previousTotal: Int = 0) -> Int {
    guard n > 0 else { return previousTotal }
    return sum(to: n - 1, previousTotal: previousTotal + n)
}

Release builds are smart enough to optimize this tail-recursion (through a process called “tail call optimization”, TCO, also known as “tail call elimination”), mitigating the stack growth for the recursive calls. WWDC 2015 Profiling in Depth , while on a different topic, the time profiler, shows exactly what's happening when it optimizes tail calls.

The net effect is that if your recursive routine is employing tail calls, release builds can use tail call elimination to mitigate stack memory issues, but debug (non-optimized) builds will not do this.

EXEC_BAD_ACCESS usually means that you are trying to access an object which is not in memory or probably not properly initialized.

Check in your code, if you are accessing your Dictionary variable after it is somehow removed? is your variable properly initialized? You might have declared the variable but did not initialize it and accessing it.

There could be a ton of reasons and cant say much without seeing any code.

Try to turn on NSZombieOjects - this might provide you more debug information. Refer to here How to enable NSZombie in Xcode?

IF you would like to know where and when exactly is the error occurring, you could check for memory leaks using instruments. This might be helpful http://www.raywenderlich.com/2696/instruments-tutorial-for-ios-how-to-debug-memory-leaks

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM