Post

Replies

Boosts

Views

Activity

IOS warning: instance method 'userNotificationCenter(_:willPresent:withCompletionHandler:)' nearly matches optional requirement
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+.
3
1
1k
Feb ’24
Deep links in AppleWatch
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.
0
1
1.1k
Mar ’24
Notification Service Extension restrictions
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?
3
1
1.2k
Jun ’24
Moving SceneDelegate to a different target
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) -&gt; 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 &lt;key&gt;UIApplicationSceneManifest&lt;/key&gt; &lt;dict&gt; &lt;key&gt;UIApplicationSupportsMultipleScenes&lt;/key&gt; &lt;true/&gt; &lt;key&gt;UISceneConfigurations&lt;/key&gt; &lt;dict&gt; &lt;key&gt;UIWindowSceneSessionRoleApplication&lt;/key&gt; &lt;array&gt; &lt;dict&gt; &lt;key&gt;UISceneConfigurationName&lt;/key&gt; &lt;string&gt;Default Configuration&lt;/string&gt; &lt;key&gt;UISceneDelegateClassName&lt;/key&gt; &lt;string&gt;Presentation.SceneDelegate&lt;/string&gt; &lt;/dict&gt; &lt;/array&gt; &lt;/dict&gt; &lt;/dict&gt; // In the AppDelegate public func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -&gt; 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?
0
1
449
Feb ’25
SwiftUI Vs AppKit for a complex and dynamic UI
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?
1
1
512
Feb ’25
Periodic iOS background execution
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.
2
3
3.3k
Feb ’23
Timed-Wait for main thread
The scenario is, in a macOS app (primarly), main thread needs to wait for some time for a certain 'event'. When that event occurs, the main thread is signaled, it gets unblocked and moves on. An example is, during shutdown, a special thread known as shutdown thread waits for all other worker threads to return (thread join operation). When all threads have returned, the shutdown thread signals the main thread, which was waiting on a timer, to continue with the shutdown flow. If shutdown thread signals the main thread before the later's timer expires, it means all threads have returned. If main thread's timer expires first, it means some threads have failed to join (probably stuck in infinite loop due to bug, disk I/O etc.). This post is to understand how main thread can wait for some time for the shutdown thread. There are two ways: a) dispatch_semaphore_t b) pthread conditional variable (pthread_cond_t) and mutex (pthread_mutex_t). Expanding a bit on option (b) using conditional variable and mutex: // This method is invoked on the main thread bool ConditionSignal::TimedWait() noexcept { struct timespec ts; pthread_mutex_t * mutex = (pthread_mutex_t *) (&vPosix.vMutexStorage[0]); pthread_cond_t * cond = (pthread_cond_t *) (&vPosix.vCondVarStorage[0]); // Set the timer to 3 sec. clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 3; pthread_mutex_lock(mutex); LOG("Main thread has acquired the mutex and is waiting!"); int wait_result = pthread_cond_timedwait(cond, mutex, &ts); switch (wait_result) { case 0: LOG("Main thread signaled!"); return true; case ETIMEDOUT: LOG("Main thread's timer expired!"); return false; default: LOG("Error: Unexpected return value from pthread_cond_timedwait: " + std::to_string(wait_result)); break; } return false; } // This method is invoked on shutdown thread after all worker threads have returned. void ConditionSignal::Raise() noexcept { pthread_mutex_t * mutex = (pthread_mutex_t *) (&vPosix.vMutexStorage); pthread_cond_t * cond = (pthread_cond_t *) (&vPosix.vCondVarStorage); pthread_mutex_lock(mutex); LOG("[Shutdown thread]: Signalling main thread..."); pthread_cond_signal(cond); pthread_mutex_unlock(mutex); } Both options allow the main thread to wait for some time (for shutdown thread) and continue execution. However, when using dispatch_semaphore_t, I get the following warning: Thread Performance Checker: Thread running at User-interactive quality-of-service class waiting on a lower QoS thread running at Default quality-of-service class. Investigate ways to avoid priority inversions Whereas, with conditional variables, there are no warnings. I understand the warning - holding the main thread can prevent it from responding to user events, thus causing the app to freeze. And in iOS, the process is killed if main thread takes more than 5 secs to return from applicationWillTerminate(_:) delegate method. But in this scenario, the main thread is on a timed-wait, for some milliseconds i.e., it is guaranteed to not get blocked indefinitely. While this is described for macOS, this functionality is required for all Apple OSes. What is the recommend way?
3
0
145
Feb ’26
IOS warning: instance method 'userNotificationCenter(_:willPresent:withCompletionHandler:)' nearly matches optional requirement
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+.
Replies
3
Boosts
1
Views
1k
Activity
Feb ’24
Deep links in AppleWatch
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.
Replies
0
Boosts
1
Views
1.1k
Activity
Mar ’24
Notification Service Extension restrictions
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?
Replies
3
Boosts
1
Views
1.2k
Activity
Jun ’24
Moving SceneDelegate to a different target
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) -&gt; 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 &lt;key&gt;UIApplicationSceneManifest&lt;/key&gt; &lt;dict&gt; &lt;key&gt;UIApplicationSupportsMultipleScenes&lt;/key&gt; &lt;true/&gt; &lt;key&gt;UISceneConfigurations&lt;/key&gt; &lt;dict&gt; &lt;key&gt;UIWindowSceneSessionRoleApplication&lt;/key&gt; &lt;array&gt; &lt;dict&gt; &lt;key&gt;UISceneConfigurationName&lt;/key&gt; &lt;string&gt;Default Configuration&lt;/string&gt; &lt;key&gt;UISceneDelegateClassName&lt;/key&gt; &lt;string&gt;Presentation.SceneDelegate&lt;/string&gt; &lt;/dict&gt; &lt;/array&gt; &lt;/dict&gt; &lt;/dict&gt; // In the AppDelegate public func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -&gt; 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?
Replies
0
Boosts
1
Views
449
Activity
Feb ’25
SwiftUI Vs AppKit for a complex and dynamic UI
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?
Replies
1
Boosts
1
Views
512
Activity
Feb ’25
Periodic iOS background execution
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.
Replies
2
Boosts
3
Views
3.3k
Activity
Feb ’23
Timed-Wait for main thread
The scenario is, in a macOS app (primarly), main thread needs to wait for some time for a certain 'event'. When that event occurs, the main thread is signaled, it gets unblocked and moves on. An example is, during shutdown, a special thread known as shutdown thread waits for all other worker threads to return (thread join operation). When all threads have returned, the shutdown thread signals the main thread, which was waiting on a timer, to continue with the shutdown flow. If shutdown thread signals the main thread before the later's timer expires, it means all threads have returned. If main thread's timer expires first, it means some threads have failed to join (probably stuck in infinite loop due to bug, disk I/O etc.). This post is to understand how main thread can wait for some time for the shutdown thread. There are two ways: a) dispatch_semaphore_t b) pthread conditional variable (pthread_cond_t) and mutex (pthread_mutex_t). Expanding a bit on option (b) using conditional variable and mutex: // This method is invoked on the main thread bool ConditionSignal::TimedWait() noexcept { struct timespec ts; pthread_mutex_t * mutex = (pthread_mutex_t *) (&vPosix.vMutexStorage[0]); pthread_cond_t * cond = (pthread_cond_t *) (&vPosix.vCondVarStorage[0]); // Set the timer to 3 sec. clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 3; pthread_mutex_lock(mutex); LOG("Main thread has acquired the mutex and is waiting!"); int wait_result = pthread_cond_timedwait(cond, mutex, &ts); switch (wait_result) { case 0: LOG("Main thread signaled!"); return true; case ETIMEDOUT: LOG("Main thread's timer expired!"); return false; default: LOG("Error: Unexpected return value from pthread_cond_timedwait: " + std::to_string(wait_result)); break; } return false; } // This method is invoked on shutdown thread after all worker threads have returned. void ConditionSignal::Raise() noexcept { pthread_mutex_t * mutex = (pthread_mutex_t *) (&vPosix.vMutexStorage); pthread_cond_t * cond = (pthread_cond_t *) (&vPosix.vCondVarStorage); pthread_mutex_lock(mutex); LOG("[Shutdown thread]: Signalling main thread..."); pthread_cond_signal(cond); pthread_mutex_unlock(mutex); } Both options allow the main thread to wait for some time (for shutdown thread) and continue execution. However, when using dispatch_semaphore_t, I get the following warning: Thread Performance Checker: Thread running at User-interactive quality-of-service class waiting on a lower QoS thread running at Default quality-of-service class. Investigate ways to avoid priority inversions Whereas, with conditional variables, there are no warnings. I understand the warning - holding the main thread can prevent it from responding to user events, thus causing the app to freeze. And in iOS, the process is killed if main thread takes more than 5 secs to return from applicationWillTerminate(_:) delegate method. But in this scenario, the main thread is on a timed-wait, for some milliseconds i.e., it is guaranteed to not get blocked indefinitely. While this is described for macOS, this functionality is required for all Apple OSes. What is the recommend way?
Replies
3
Boosts
0
Views
145
Activity
Feb ’26