I have an app whose logic is in C++ and rest of the parts (UI) are in Swift and SwiftUI.
Exceptions can occur in C++ and Swift. I've got the C++ part covered by using the Linux's signal handler mechanism to trap signals which get raised due to exceptions.
But how should I capture exceptions in Swift? When I say exceptions in Swift, I mean, divide by zero, force unwrapping of an optional containing nil, out of index access in an array, etc. Basically, anything that can go wrong, I don't want my app to abruptly crash... I need a chance to finalise my stuff, alert the user, prepare diagnostic reports and terminate. I'm looking for a 'catch-all' exception handler. As an example, let's take Android. In Android, there is the setDefaultUncaughtExceptionHandler method to register for all kinds of exceptions in any thread in Kotlin. I'm looking for something similar in Swift that should work for macOS, iOS & iPadOS, tvOS and watchOS.
I first came across the NSSetUncaughtExceptionHandler method. My understanding is, this only works when I explicitly raise NSExceptions. When I tested it, observed that the exception handler didn't get invoked for either case - divide by zero or invoking raise.
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
Log("AppDelegate.applicationDidFinishLaunching(_:)")
// Set the 'catch-all' exception handler for Swift exceptions.
Log("Registering exception handler using NSSetUncaughtExceptionHandler()...")
NSSetUncaughtExceptionHandler { (exception: NSException) in
Log("AppDelegate.NSUncaughtExceptionHandler()")
Log("Exception: \(exception)")
}
Log("Registering exception handler using NSSetUncaughtExceptionHandler() succeeded!")
// For C++, use the Linux's signal mechanism.
ExceptionHandlingCpp.RegisterSignals()
//ExceptionHandlingCpp.TestExceptionHandler()
AppDelegate.TestExceptionHandlerSwift()
}
static func TestExceptionHandlerSwift() {
Log("AppDelegate.TestExceptionHandlerSwift()")
DivisionByZero(0)
}
private static func DivisionByZero(_ divisor: Int) {
Log("AppDelegate.DivisionByZero()")
let num1: Int = 2
Log("Raising Exception...")
//let result: Int = num1/divisor
let exception: NSException = NSException(name: NSExceptionName(rawValue: "arbitrary"), reason: "arbitrary reason", userInfo: nil)
exception.raise()
Log("Returning from DivisionByZero()")
}
}
In the above code, dividing by zero, nor raising a NSException invokes the closure passed to NSSetUncaughtExceptionHandler, evident from the following output logs
AppDelegate.applicationWillFinishLaunching(_:)
AppDelegate.applicationDidFinishLaunching(_:)
Registering exception handler using NSSetUncaughtExceptionHandler()...
Registering exception handler using NSSetUncaughtExceptionHandler() succeeded!
ExceptionHandlingCpp::RegisterSignals()
....
AppDelegate.TestExceptionHandlerSwift()
AppDelegate.DivisionByZero()
Raising Exception...
Currently, I'm reading about ExceptionHandling framework, but this is valid only for macOS.
What is the recommended way to capture runtime issues in Swift?
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Created
I have an app whose logic is in C++ and rest of the parts (UI) are in Swift and SwiftUI.
When an exception is raised by some C++ code, I'm using the Linux signal handler mechanism to trap it. From my previous post, I understand that fatal exceptions like SIGSEGV, SIGBUS, SIGFPE etc., there's nothing much that can be done by the process. My only intent for using a signal handler is to log something, so that it becomes easy to fix during development. Ofc, even that logging can fail, based on the severity of the exception, but that's okay... make an attempt to log - if it works, great, else the process can terminate.
I'm registering for SIGSEGV and SIGFPE with the following code
// ExceptionHandlingCpp.hpp file
struct tSignals {
SignalHandlerFunc signalHandlerFunc;
uint32_t signal;
[[maybe_unused]]
uint8_t reserved[4];
};
// ExceptionHandlingCpp.cpp file
tSignals ExceptionHandlingCpp::unixSignals[] = {
{HandleSignals, SIGFPE, {0}},
{HandleSignals, SIGSEGV, {0}},
{HandleSignals, SIGKILL, {0}},
};
std::string ExceptionHandlingCpp::signalToString(int signal) {
switch(signal) {
case SIGFPE:
return "SIGFPE";
case SIGSEGV:
return "SIGSEGV";
case SIGKILL:
return "SIGKILL";
default:
return "Unknown signal";
}
}
void ExceptionHandlingCpp::RegisterSignals() {
LOG("ExceptionHandlingCpp::RegisterSignals()");
struct sigaction sa;
sa.sa_flags = SA_SIGINFO;
for(int i = 0; i < sizeof(unixSignals)/sizeof(tSignals); ++i) {
sa.sa_sigaction = unixSignals[i].signalHandlerFunc;
if(sigaction(unixSignals[i].signal, &sa, nullptr) == 1) {
LOG("Failed to set " + signalToString(unixSignals[i].signal) + "'s signal handler!");
} else {
LOG(signalToString(unixSignals[i].signal) + "'s signal handler set sucessfully!");
}
}
}
In my signal handler (HandleSignals method), immediately after trapping a signal, I log something and set the default handler... This breaks out of the loop that occurs when returning from the signal handler.
// ExceptionHandlingCpp.cpp
void ExceptionHandlingCpp::HandleSignals(int pSignal, siginfo_t *pInfo, void *pContext) {
LOG("ExceptionHandlingCpp::HandleSignals(int, signinfo_t*, void*)");
LOG("signal = " + signalToString(pSignal));
UnregisterSignals(pSignal);
LOG("Returning from exception handler...");
}
void ExceptionHandlingCpp::UnregisterSignals(int pSignal) {
LOG("UnregisterSignals(int)");
struct sigaction defaultAction {};
defaultAction.sa_handler = SIG_DFL;
if(sigaction(pSignal, &defaultAction, nullptr) == -1) {
LOG("Error in resetting action for " + signalToString(pSignal));
} else {
LOG("Successfully reset " + signalToString(pSignal) + "'s action to default!");
}
}
When I test this code by raising SIGSEGV (as shown below),
void ExceptionHandlingCpp::DereferenceNullPtr ()
{
LOG("DereferenceNullPtr()");
int* ptr = nullptr;
LOG("Raising exception...");
int value = *ptr;
}
everything works as expected. Signal handler is invoked, default handler is set and the process immediately quits. But when I try to raise a SIGFPE,
void* ExceptionHandlingCpp::DivisionByZero ([[maybe_unused]] void* pParms)
{
LOG("DivisionByZero()");
int num1;
int num2;
int result;
num1 = 5;
num2 = 0;
LOG("Raising exception...");
result = num1 / num2;
LOG("Returning from DivisionByZero() method");
return nullptr;
}
my signal handler is not invoked (as shown in the logs below). The process doesn't terminate either. It seems that the flow simply 'walks over' this division by zero instruction as if nothing happened and returns from that method, which shouldn't have happened, as the process should've terminated after reaching my signal handler.
RegisterSignals()
SIGFPE's signal handler set sucessfully!
SIGSEGV's signal handler set sucessfully!
SIGKILL's signal handler set sucessfully!
....
DivisionByZero()
Raising exception...
Returning from DivisionByZero() method
....
AppDelegate.applicationWillBecomeActive(_:)
AppDelegate.applicationDidBecomeActive(_:)
...
// UI is displayed
Why is SIGFPE not raised? What am I missing here?
I have a SwiftUI project which has the following hierarchy:
IOSSceneDelegate (App target) - depends on EntryPoint and Presentation static libs.
Presentation (Static library) - Depends on EntryPoint static lib. Contains UI related logic and updates the UI after querying the data layer.
EntryPoint (Static library) - Contains the entry point, AppDelegate (for its lifecycle aspects) etc.
I've only listed the relevant targets here.
SceneDelegate was initially present in EntryPoint library, because the AppDelegate references it when a scene is created.
public func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Set the SceneDelegate dynamically
let sceneConfig: UISceneConfiguration = UISceneConfiguration(name: "mainWindow", sessionRole: connectingSceneSession.role)
sceneConfig.delegateClass = SceneDelegate.self
return sceneConfig
}
The intent is to move the SceneDelegate to the Presentation library.
When moved, the EntryPoint library fails to compile because it's referencing the SceneDelegate (as shown above).
To remove this reference, I tried to set up the SceneDelegate in the old way - In the info.plist file, mention a SceneConfiguration and set the SceneDelegate in Presentation.
// In the Info.plist file
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>Presentation.SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
// In the AppDelegate
public func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Refer to a static UISceneconfiguration listed in the info.plist file
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
As shown above, the Presentation.SceneDelegate is referred in the Info.plist file and the reference is removed from the AppDelegate (in EntryPoint library).
The app target compiles, but when I run it, the SceneDelegate is not invoked. None of the methods from the SceneDelegate (scene(_:willConnectTo:options:), sceneDidDisconnect(_:), sceneDidEnterBackground(_:) etc.) are invoked. I only get the AppDelegate logs.
It seems like the Configuration is ignored because it was incorrect. Any thoughts? Is it possible to move the SceneDelegate in this situation?
I'm looking to develop a very rich networking macOS app (like social media apps) operated by very large number of users, each user is able to create a number of windows, operate/view each of them, able to customize the app to his liking etc. The UI is expected to be very rich and dynamic.
The question is, should I choose AppKit or SwiftUI?
I have a basic understanding of SwiftUI, its declarative way of defining UI layouts and populating it with data. Not sure if SwiftUI can handle a very rich and dynamic UI customised by large number of users.
Any thoughts? What works best in this scenario? What is Apple's recommendation?
In an iPadOS SwiftUI app supporting multiple scenes, each Scene responds to a particular way in which the app was launched. If app was launched by tapping an associated file or a deep link (custom URL), then, the URLHandlerScene is invoked. If app was launched by QuickAction (long tap on the app icon), then another Scene is invoked etc. Each Scene has a purpose and responds to a particular launch.
But after defining handlesExternlEvents(matching:) scene modifier, the scene was not getting launched when user taps the associated file or the app's Deeplinks was invoked.
@main
struct IOSSwiftUIScenesApp: App {
var body: some Scene {
DefaultScene()
URLHandlerScene()
.handlesExternalEvents(matching: ["file://"]) // Launched by an associated file
.handlesExternalEvents(matching: ["Companion://"]) // Launched by Deeplink.
// Other scenes
}
}
struct URLHandlerScene: Scene {
@State private var inputURL: URL // Store the incoming URL
init() {
self.inputURL = URL(string: "Temp://")!
}
var body: some Scene {
WindowGroup {
URLhandlerView(inputURL: $inputURL)
.onOpenURL(perform: { (fileURL: URL) in
log(String(format: "URLhandlerView().onOpenURL | Thread.current = %@", String(describing: Thread.current)))
log("fileURL = " + String(describing: fileURL))
inputURL = fileURL
})
}
}
}
As shown above, I've attached handlesExternalEvents(matching:) modifier with "file://" for the associate file and "Companion" is my custom URL scheme. As per the scene matching rules documented here, my URLHandlerScene should get launched, but every time I launch the app using associated file or 'open' a Deeplink, the DefaultScene is always launched.
What is missing here? Can someone please help?
I have created a sample app which read/write from a network file. When the file was attempted to open (using open Linux API), connection to network file was lost. The thread which was stuck on the open method, returns after a long time.
It was observed that for macOS, the maximum return time of the thread was around 10 mins, whereas in Windows and Linux, the maximum timeout was 60 sec and 90 sec.
macOS has a very large timeout before returning the thread with a network failure error. Is this by designed and expected? With a large timeout as 10mins, it's difficult to respond swiftly back to the user.