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?
Post
Replies
Boosts
Views
Activity
I have a watchOS app written in C++, Swift and SwiftUI. This app is meant only for 64-bit architecture. I know Apple uses the arm architecture, so I thought arm64 will be the arch of latest AppleWatches.
I tested my code in x64 simulator (Intel Mac) and arm64 simulator (silicon Mac) - no trouble. Next is to test on a device - AppleWatch Series 9, model: A2984. I set the arch to arm64 and my application failed to build, with Xcode showing a popup about arch mismatch... which is due to the app's arch begin different from device's arch. When I change Xcode arch setting to arm64_32, then I don't get this popup, but my builds fail. When I read about arm64_32, I realised that this is a version of arm64 with 32 bit pointers... not the arm64 with 64 bit pointers.
Looking at the specification of AppleWatch Series 9, Apple has confirmed that it has 64 bit CPU, but the instruction set is unknown. This wiki page about different AppleWatch CPUs is marked TBC for AppleWatch Series 9. From Xcode, I got to know that this unconfirmed arch is arm64_32. This completely breaks my code. Are there any 64-bit watches with 64-bit pointer sizes? What is Apple's future in this area? Do they plan to release AppleWatches that support 64-bit pointers or it's always going to be 32?
I have an iOS app which uses Notification Service Extension (NSE) to process incoming notifications before it displayed to user.
In NSE, as part of initialization of my app, I need to iterate through a 2D array. There are roughly 65k iterations. I've noticed that this iteration fails somewhere in between and the NSE process crashes... I can say it crashes because the logs stopped in between the iterations. This results in 'unmodified notification' getting displayed immediately, whereas NSE is granted 30 sec of background execution.
My question is, why does this happen? The above iteration of 2D array works in app, but fails in NSE. Is there some kind of restriction on background extensions? - the documentation only talks about a time limit of 30sec. If there is some kind of restriction (like CPU and memory), how does one know this and code for it... since Apple did not provide any documentation. Or perhaps, there is a completely different reason?
This page describes the procedure to create deep links in iOS. I was able to launch an IOS Companion app (name of the app in my case) using its deep link.
But the same is not working in AppleWatch. This is my plist to register a custom scheme - Companion:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<!-- <key>CFBundleTypeRole</key>
<string>Viewer</string> -->
<key>CFBundleURLName</key>
<string><some unique ID></string>
<key>CFBundleURLSchemes</key>
<array>
<string>Companion</string>
</array>
</dict>
</array>
</dict>
</plist>
I have implemented onOpenURL(perform:) to handle app launches using a deep link (url).
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL(perform: { (link: URL) in
Log(String(format: "Link = %@", link.absoluteString))
// Use then deep link
})
}
}
In iOS, I tested deep links in two ways:
Wrote the full deep link in Notes app and tapped it.
Created another app called AppLauncher with a Button saying 'Launch using Deep link'.... which when clicked opens the deep link using open(_:options:completionHandler:).
Both the approaches work in iOS, but in watchOS, I can only try 2 because Notes app is not available for AppleWatch. So, I created another watchOS app called AppLauncher, which displays a SwiftUI Button saying 'Launch using Deep link', which when tapped, tries to open the link using openSystemURL(_:). But as mentioned in the documentation (linked earlier),
Opens the specified system URL.
this API only works for links associated with System apps i.e., Apple's call and message apps.
So, how else can I use deep link to launch another app? I believe it's possible to launch an app using its deep link because the info.plist keys required to define a deep link scheme association (CFBundleURLTypes, CFBundleURLSchemes etc) is valid for watchOS too.
I have an iOS project with the following targets:
SwiftExtensions (AppTarget) -> depends on Experience
Experience (StaticLibrary) -> depends on Lifecycle
Lifecycle (StaticLibrary)
I have defined the SceneDelegate in Lifecycle library:
public class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// scene(_:willConnectTo:options:) is implemented in Experience
// scene(_:openURLContexts:) is implemented in Experience
// Other methods such as sceneWillEnterForeground(_:), sceneDidBecomeActive(_:) etc.
}
As shown above, scene(_:willConnectTo:options:) and scene(_:openURLContexts:) are not defined here.
In the Experience library, SceneDelegate is extended:
extension SceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
NSLog("[Experience]: SceneDelegate.scene(_:willConnectTo:options:)")
if (connectionOptions.urlContexts.isEmpty) {
NSLog("[Experience]: Not launched using file!")
} else {
NSLog("[Experience]: Launched using file!")
let urlContexts: Set<UIOpenURLContext> = connectionOptions.urlContexts
for (index, urlContext) in urlContexts.enumerated() {
NSLog(String(format: "[Experience]: url[%d] = %@ ", index, urlContext.url.path()))
}
}
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
NSLog("[Experience]: SceneDelegate.scene(_:openURLContexts:)")
for (index, urlContext) in URLContexts.enumerated() {
NSLog(String(format: "[Experience]: url[%d] = %@ ", index, urlContext.url.path()))
}
}
}
Now, when I tap the app icon, scene(_:willConnectTo:options:) is not invoked. When I tap an associated file type, scene(_:willConnectTo:options:) is not invoked, again. If app is running in background, and I foreground the app by tapping an associated file type, scene(_:openURLContexts:) is not invoked.
Basically, when I define these two methods outside the target, despite with public access modifier, iOS doesn't invoke my delegate methods defined in the other library. My understanding was, Extensions can be used to break up the class. What is missing here?
In iPhone, if you open Files app and tap on an associated file type, its corresponding app is launched. I have implemented this functionality in my iOS SwiftUI app - associated a file type - .csk, with my iOS app and handled its launch using the onOpenURL(perform:) modifier.
WindowGroup {
FileContentView()
.onOpenURL(perform: { (fileUrl: URL) in
Log(String(format: "File Url = %@", fileUrl.absoluteString))
// Update UI with the content of the file
})
}
I want to achieve the same for my watchOS app. I noticed that the UTExportedTypesDeclaration key, which is used to declare the file types created and owned by the app, is not available for watchOS. But the CFBundleDocumentTypes key, which is used to associate a file type with the app is available. So, I expect my app to respond to taps on those associated file types, but I've run into a snag. There is no Files app in AppleWatch nor is there a way to view my iCloud storage. I have my .csk file (which is associated with the watchOS app) in iCloud.
So, how can I test this launch? How can an AppleWatch user tap a file and view it in his app? It should be possible to handle file launches since CFBundleDocumentTypes is available for watchOS.
I have an iOS app, which uses Notification Service Extension (NSE) for its functioning. When I build the app, I get the following error:
error: Embedded binary's bundle identifier is not prefixed with the parent app's bundle identifier.
Embedded Binary Bundle Identifier: (null)
Parent App Bundle Identifier: com.example.sample
This error occurs when NSE doesn't have the app's bundle identifier prefixed to it (Reference post).
In my case, the bundle ID of the app is com.example.sample and the bundle ID of the extension, com.example.sample.NSESample (checked these values in Project -> Target -> Signing & capabilities -> bundle identifier label). I believe this is as expected. I don't understand this error now, which says the embedded bundle identifier is null. I cleaned and built the app again..... same error.
According to this stackoverflow post, you have to enable 'Copy only when installing'. When I tried, it worked. I don't know what this setting means and how this solves the issue. I'm unable to find any documentation reg this setting. Idk if this is even a valid solution, because the error says 'Embedded Binary Bundle Identifier' is null.
Why is the embedded bundle identifier null (despite the Xcode interface showing a valid bundle ID)? How can I solve this?
In an iOS project, I have implemented the following delegate method to handle Notifications when app is in foregorund:
func userNotificationCenter (_ pNotificationCenter : UNUserNotificationCenter, willPresent pNotification : UNNotification, withCompletionHandler pCompletionHandler : @escaping (UNNotificationPresentationOptions) -> Void) -> Void
{
// When app is in foreground, notification is directly sent to
// the app by invoking this delegate method.
}
When building, I'm getting the warning. But my implementation is correct according to documentation.
Why is this method not recognized as the implementation of the UNUserNotificationCenterDelegate protocol?
This is the warning in text format:
Showing All Issues
<file path & line number>: Instance method 'userNotificationCenter(_:willPresent:withCompletionHandler:)' nearly matches optional requirement 'userNotificationCenter(_:willPresent:withCompletionHandler:)' of protocol 'UNUserNotificationCenterDelegate'
This is the image of the warning:
Am I missing some flag in a compiler setting?
My env:
Xcode 15.2, Swift 5.0, Deployment target - iOS 14.0+.
I have a SwiftUI app which supports a launch from a configured universal link. I'm following this documentation.
When the app is not running and I tap a universal link, app launches. This proves the association of the website and the app.
Now, when the app is already running in the background, and then I tap a universal link, things don't work as expected. Quoting from the documentation
If your app has opted into Scenes, and your app is not running, the system delivers the universal link to the scene(:willConnectTo:options:) delegate method after launch, and to scene(:continue:) when the universal link is tapped while your app is running or suspended in memory.
I've implemented scene(_:continue:) in my SceneDelegate but it never gets invoked. I'm pasting some relevant logs
AppDelegate.application(_:willFinishLaunchingWithOptions:)
AppDelegate.application(_:didFinishLaunchingWithOptions:)
AppDelegate.application(_:configurationForConnecting:options:)
SceneDelegate.scene(_:willConnectTo:options:)
SceneDelegate.sceneWillEnterForeground(_:)
NSNotificationName(_rawValue: UIApplicationWillEnterForegroundNotification)
SceneDelegate.sceneDidBecomeActive(_:)
NSNotificationName(_rawValue: UIApplicationDidBecomeActiveNotification)
Now, I background the app...
SceneDelegate.sceneWillResignActive(_:)
NSNotificationName(_rawValue: UIApplicationWillResignActiveNotification)
SceneDelegate.sceneDidEnterBackground(_:)
NSNotificationName(_rawValue: UIApplicationDidEnterBackgroundNotification)
App runs in the background... Now, I tap a Universal link
SceneDelegate.sceneWillEnterForeground(_:)
NSNotificationName(_rawValue: UIApplicationWillEnterForegroundNotification)
SceneDelegate.sceneDidBecomeActive(_:)
NSNotificationName(_rawValue: UIApplicationDidBecomeActiveNotification)
As shown in the logs above, there is no trace of scene(_:continue:).
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
Log("SceneDelegate.scene(_:continue:)")
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let universalLink = userActivity.webpageURL else {
Log("Not launched via universal links!")
return
}
Log(String(format: "userActivities = %@", String(describing: userActivity)))
Log(String(format: "universalLink = %@", universalLink.absoluteString))
StaticContext.dataFromMainApp = universalLink.absoluteString
StaticContext.viewController.updateLabelWithLink()
}
What am I missing here?
I've reached out to AppleDTS for code-level support on Dec 19th 2023. It's been 3 weeks now (Jan 11, 2024). I sent a follow-up 3 days ago, but no response yet. Their response time is supposed to be within 3 business days.. experiencing this level of delay in a paid support is disappointing.
I'm not raising a new TSI but resuming an old one after almost 8 months because it is a continuation of the same problem. Delayed response from my side is a problem? I hope not (I didn't find any restrictions in the documentation).
What should I do in this situation?
According to swift documentation, the following describes the various forms of the Import declaration.
Grammar of an import declaration
import-declaration → attributes? import import-kind? import-path
import-kind → typealias | struct | class | enum | protocol | let | var | func
import-path → identifier | identifier . import-path
Questions:
Attributes and import-kind are optional. import-kind refers to the type of the import (class, struct, enum etc etc).... defined in the second line. Does attributes refers to @available which can used to add an import for a specific platform version?
In the 3rd line, I understand that identifier here refers to the names of modules, classes, funcs etc., but what does import-path mean? Can I give the actual path in disk? What all comes under this? I couldn't understand from this documentation.
There are two ways to capture application transitioning to foregorund/background and inactive/active. Lets take the event received when app transitions to the inactive state as an example.
Implement the corresponding AppDelegate method - applicationWillResignActive(_:)
func applicationWillResignActive(_ application: UIApplication) {
Log("applciationWillResignActive(_:)")
}
Register for willResignActiveNotification during startup
NotificationCenter.default.addObserver(forName:UIApplication.willResignActiveNotification, object: nil, queue: .main) { (broadcastNotification: Notification) in
Log("Received " + String(describing: broadcastNotification.name))
}
When I tested in code, both works. First, the applicationWillResignActive(_:) delegate method is invoked and then, willResignActiveNotification is received (as mentioned in the documentation).
But I'm unable to decide which to use... even after reading their documentation. What is the recommendation? If both are fine, then there are two different ways to achieve the same thing... there's gotta be some purpose, right?
I have a sample app to test push notifications. The app has a Notification Service Extension to intercept remote notifications, update/enhance the content and then display it to user.
But there are some scenarios where the app shouldn't end up displaying a notification to user... Like, due to slow internet, the time taken to download media content (like images) exceeds the 30 sec time limit. There are more app specific scenarios. Basically, every push received from APNS need not be shown to user. Notification Service Extension did not have this power to suppress remote notifications. In the end, it will always display a notification to user.
From iOS 13.3 onwards, Notification Service Extension has the ability to filter out these remote notifications. The first step is (quoting form the documentation),
This entitlement allows a notification service extension to receive remote notifications without displaying the notification to the user. To apply for this entitlement, see Request Notification Service Entitlement.
When I click on the hyperlink (you need to be an account holder to view that page), Apple asks for some info like 'App Store URL', 'When extension runs, what system and network resources does it need?' and so on. I only have a sample app to help understand the aspects of notifications. I don't have any full-fledged app in AppStore... yet... and I have no clue what all my full-fledged app will be doing in the extension (since I'm just in learning stage). I only have a sample app, where I log and perform some basic string operations when my extension is invoked.
Due to this, I'm unable to see the extension filtering out notifications.
How does anyone test this feature? Has anyone tried it? Really appreciate any guidance. I'm not sure how to proceed..
UserNotifications framework helps the app to handle all notification related aspects. The userNotificationCenter(_:didReceive:withCompletionHandler:) delegate method is used to handle user's action (dismiss, selected button, tap, text input etc.). As evident from the documentation, this method is available in iOS and iPadOS but not tvOS.
The @available attribute is one way to exclude methods.
@available(iOS 10.0, *)
func userNotificationCenter (_ pNotificationCenter: UNUserNotificationCenter, didReceive pResponse: UNNotificationResponse, withCompletionHandler pCompletionHandler: @escaping () -> Void) -> Void {
// Handle user's action on a notification.
...
}
When the delegate method is declared as above, I expect it to be included only for iPadOS and iOS. When building for tvOS, I get the following error:
error: 'UNNotificationResponse' is unavailable in tvOS
error: cannot override 'userNotificationCenter' which has been marked unavailable
Clearly, it is included for tvOS also.
Another approach is using
#if os(iOS)
and it does work... I don't get the above errors, meaning the code is not included for tvOS. But I want to understand how to achieve this using @available attribute because I have other situations where I need to exclude methods for certain versions of the same OS.
How do I exclude userNotificationCenter(_:didReceive:withCompletionHandler:) for tvOS using @available attribute?
An app has some periodic light background work, which is expected to run even when app is suspended.
Ofc, if user has swiped up, nothing can be done. When swiped from app switcher, if app was in Suspended state, its not informed else, in every other state (including background), applicationWillTerminate(_:) is invoked
When application is not killed by user and is in suspended state, I want to run some small tasks at certain intervals.
According to this documentation, Background fetch is the way to go. This post by Apple DTS also suggests the same i.e., Background Fetch (BGAppRefreshTask) for periodic background execution.
Example:
The sample background task is to count 10s in multiple of 10s.
Launch handler is registered with the background task ID configured in info.plist file in application(_:didFinishLauchingWithOptions:).
I have the following code invoked from in applicationDidEnterBackground(_:),
func initiateBackgroundTask(id: String) {
let currentDate: Date = Date.now
guard let futureDate: Date = Calendar.current.date(byAdding: .minute, value: 1, to: currentDate) else {
NSLog(AppDelegate.TAG + "Error in obtaining future date!")
return
}
let taskRequest: BGAppRefreshTaskRequest = BGAppRefreshTaskRequest(identifier: id)
// Schedule background task
taskRequest.earliestBeginDate = futureDate
do {
// Note: Background processing doesn't work in Simulator.
// Submitting in Simulator leads to BGTaskScheduler.Error.Code.unavailable error.
try BGTaskScheduler.shared.submit(taskRequest)
NSLog(AppDelegate.TAG + "Task submitted successfully!")
} catch {
NSLog(AppDelegate.TAG + "error = " + String(describing: error))
return
}
NSLog(AppDelegate.TAG + "Background task scheduled successfully!!")
NSLog(AppDelegate.TAG + "Scheduled date = %@ | Present date = %@", String(describing: taskRequest.earliestBeginDate), String(describing: Date.now))
}
The following method is invoked from launch handler:
func taskRefreshCount1Min(task: BGAppRefreshTask) {
// Set expiration handler
task.expirationHandler = {
NSLog(AppDelegate.TAG + "Background execution time about to expire!")
NSLog(AppDelegate.TAG + "Remaining time = %fsec | %@", AppDelegate.app.backgroundTimeRemaining, String(describing: Date.now))
NSLog(AppDelegate.TAG + "Unable to complete task!!")
task.setTaskCompleted(success: false)
}
for i in 1...6 {
let presentDate: Date = Date.now
NSLog(AppDelegate.TAG + "%d) %@", i, String(describing: presentDate))
Thread.sleep(forTimeInterval: 10)
}
NSLog(AppDelegate.TAG + "Task complete!")
task.setTaskCompleted(success: true)
}
My expectation is that iOS will execute the launch handler 1 min after scheduling the background task. But that never happens, even after hours.
Task did get executed within 20 mins (Sample output shown below), but every other time, I waited for more than an hour and task was never executed.
// Output
...
Task submitted successfully!
Background task scheduled successfully!!
Scheduled date = Optional(2023-02-06 09:19:46 +0000) | Present date = 2023-02-06 09:18:46 +0000
TaskRefreshCount1Min(task:)
1) 2023-02-06 09:35:12 +0000
2) 2023-02-06 09:35:22 +0000
3) 2023-02-06 09:35:32 +0000
The documentation does state,
However, the system doesn’t guarantee launching the task at the specified date, but only that it won’t begin sooner.
But I didn't expect the results to differ by such a huge margin.
Apparently others have also faced this 'inconsistent scheduling' in iOS.
Background fetch with BGTaskScheduler works perfectly with debug simulations but never works in practice
How make iOS app running on background Every one minute in Swift?
In links I have pasted above, despite Background Fetch being the most suitable for periodic light tasks, Silent Push Notifications were suggested. I'm currently checking out Silent Push Notifications, but iOS recommends a frequency of not more than 3 per hour.
Summarising my questions from this exercise:
Is there something wrong with the code?
What technique should I use? Background fetch, silent notifications or anything else?
In Background Fetch, how do you schedule background tasks periodically? There's an instance property called earliestBeginDate to denote the minimum time after which the task can happen.