1

For metric collection threads, we want them to never crash the app. For crashes from those threads, we want catch them and report to the server without crashing the app.

On Android or Java based app, we can set uncaught exception handler for a particular thread or a thread group. See the following example.

Is there similar mechanism on iOS platform for Objective runtime level uncaught exceptions? Is there any example code as reference?

val pipelineThread = Thread({
    try {
        intArrayOf(1, 2, 3, 4, 5).asList()
            .parallelStream()
            .map { e ->
                e + 1
            }.map {
                throw RuntimeException("Pipeline map error.")
            }.collect(Collectors.toList())
    } catch (e: RuntimeException) {
        // Exceptions thrown from pipeline will be handled in the same way
        // as regular statements, even if it is parallel stream.
        throw StreamPipelineException("Java parallel stream.", e)
    }
}, "PipelineThread")
with(pipelineThread) {
    setUncaughtExceptionHandler { p0, p1 ->
        assert(p0.name.contentEquals("PipelineThread"))
        assert(p1 is StreamPipelineException)
        assert(p1.cause is RuntimeException)
        assert(p1.message.contentEquals("Java parallel stream."))
    }
}

I only find the top-level handler setting method.

burnsi
  • 6,194
  • 13
  • 17
  • 27
Jeremy
  • 86
  • 6
  • 1
    A runtime exception is always going to crash the app on iOS. You can capture it in order to report it, but then the app is going to be terminated. – Paulw11 Feb 13 '23 at 18:08

1 Answers1

2

For metric collection threads, we want them to never crash the app.

On Mac, you should move those threads into a separate process. This is how web browsers ensure that bugs impacting one tab do not impact other tabs or crash the whole browser. Usually the best tool for communicating between those processes is XPC.

On iOS, you will need to write your metric collection code carefully to avoid mistakes and not crash. You cannot spawn other processes directly in iOS today. (I keep hoping some day it will be possible. The OS can handle it. Third-party apps just aren't allowed to.)

NSSetUncaughtExceptionHandler is not intended to prevent crashes. It just gives a mechanism to record a little data or clean up certain things before crashing. Even in those cases, it must be used extremely carefully, since the system is in an undefined state during an uncaught exception. While in principle it's possible to write exception-safe Objective-C, the core frameworks are generally not. After raising an exception, there is no promise what state the system is in, and preconditions may be violated. As the docs note:

The Cocoa frameworks are generally not exception-safe. The general pattern is that exceptions are reserved for programmer error only, and the program catching such an exception should quit soon afterwards.

Assuming you're using NSThread or NSOperation for your "metric collection threads," you can create something similar to Java's approach using this, but I don't recommend it:

- (void)main {
    @try {
        ... your code ...
    } @catch ( NSException *e ) {
        // Handle the exception and end the thread/operation
    }
}

I don't recommend this because it can create difficult-to-solve bugs in distant parts of the program because the frameworks are not exception-safe. But I've seen it in enough production code to know it often works well enough. You have to decide if "app crashes and user relaunches it" is worse than "app doesn't crash but now behaves kind of weird and maybe corrupts some user data, but maybe doesn't."

But many Objective-C crashes are not due to exceptions in any case. They are due to signals, most commonly because of memory violations. In this case, the system is in an even less stable state. Even allocating or releasing memory may be invalid. (I once made the mistake of allocating memory in a signal handler, and deadlocked the program in a tight busy-loop. The Mac actually got hot. It would have been better to have just crashed.) For more, see How to prevent EXC_BAD_ACCESS from crashing an app?

Java incurs a lot of overhead, including a full virtual machine and extra bounds checks, to make it possible to catch the vast majority of error conditions (even ones you can't really recover from). It's a trade-off. It's still possible to have Java code that is not exception-safe and can fail to work correctly once an exception has been thrown, and it sometimes would be nice if Java would just crash and show you where the error is rather than programmers quietly swallowing the errors... but that's a different rant. Sometimes the sandbox makes things very nice.

Objective-C is built directly on C and allows accessing raw memory. If you do that incorrectly, it's undefined behavior and just about anything can happen, though usually the OS will kill the program. So even though it's possible to catch some kinds of exceptions, you cannot prevent all crashes.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610