Lets say there's an async operation I want to have done before returning "true" from application(didFinishLaunchingWithOptions:) -> Bool. That operation includes work that needs to be done on the main thread.
In Swift 5.4, is it possible to pause execution of the function until after some other stuff happens, then return true? I tried using a DispatchGroup and a DispatchSemaphore, but these options seems to block the main thread from proceeding, so any of the work I need to do on the main thread will be blocked and we'll get a deadlock.
Is there any way to do this in Swift 5.4? Or am I trying to do something that is simply impossible?
You should never block application(didFinishLaunchingWithOptions:), as the watchdog observes that method to know when your app is done launching, but I get your point. All attempts to make async APIs synchronous necessarily block the caller, otherwise the result isn't synchronous. You can put your work onto a background queue and wait for it, which shouldn't result in a deadlock, but blocking the main queue is never a good idea.
Is there something more specific you're trying to do?
We have existing end-to-end tests covering the app launch process that would fail if we did certain things after application(didFinishLaunchingWithOptions:) completes, because tests start running as soon as that method returns. So if I move some setup to run after it returns instead of before, then that setup won't have been done by the time those tests run, and so the tests will fail.
That's was the immediate reason why I need to block application(didFinishLaunchingWithOptions:) , which I managed to achieve via DispatchGroup enter/leave/wait (plus moving a couple of things to execute after the wait).
As to why this is all the case... I came on a project where the AppDelegate class was thousands and thousands of lines of code, much of which runs during application(didFinishLaunchingWithOptions:).
So I'm not actually looking to block that method from returning any longer than it already takes, I'm just looking for a good way to organize all its code into separate classes/modules/files while still doing the same steps before returning from the function, by breaking it up into separate chunks that can be run as something like a set of Operations in an OperationQueue rather than all in a row in one file.
Once we have it better organized and the appropriate tests updated to wait longer before they run, then it will be easy to toggle some of these steps to happen after launch completes rather than before, without risk of a regression (because we could use a remoteconfig toggle to switch back to the old way if there's a production issue).
Over all, I'm taking this approach because app launch code is an extremely high-risk area to make changes in with an app our size and age (>1M LoC, some dating back to 2011, a significant amount of Obj. C, and millions of enterprise users). I'm trying to clean it up as much as possible while preserving the ability to dynamically roll back to a state where the order in which things happen has not been altered, since there are other changes going in parallel to mine and I want to preserve the ability for QA to A/B test things in isolation.
That means moving some code out of this one method, but also, the method still needs to wait for that code to finish when we're in "original mode" if that makes sense. Because the "updated mode" has made some operations concurrent/asynchronous then I need some mechanism to make execution wait.
Unfortunately, blocking that method also blocks the entire main queue, preventing any preliminary UI setup work being done on a background thread, presumably due to the per-thread stack memory system used by iOS.
Anyway, I figured out a solution using DispatchGroup enter/leave/wait, which also required moving anything that requires to be called on the main thread to run after the wait and before the return true.