3

I have some long-running startup tasks (like loading Parse objects from local data storage) in my app. Those tasks should be finished before the interface starts to appear. The app was originally created using storyboards, so the interface starts to appear automatically after application:didFinishLaunchesWithOptions: method finishes. I can't block main thread because Parse SDK fires all it's callbacks on main thread (so blocking results in deadlock). I also need to delay return from application:didFinishLaunchesWithOptions: to finish setup. So what I did is:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Dispatch long-running tasks
    dispatch_group_t startup_group = dispatch_group_create();
    dispatch_group_async(startup_group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // Perform some long-running setup here
    });
    // Run main run loop until startup tasks finished (is it OK to do so?)
    while (dispatch_group_wait(startup_group, DISPATCH_TIME_NOW))
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
    return YES;
}

Is it the proper usage of NSRunLoop? Are there any potential caveats? Can you propose more elegant (preferably GCD) solution?

UPDATE:

  1. Loading Parse object from local data storage (i.e. loading tiny file from SSD) is not so long operation as loading something from the web. The delay is barely noticeable, but long enough to trigger warnBlockingOperationOnMainThread, so UX is not an issue.
  2. The real issue is (infrequent) crashes caused by spinning another main runloop above regular main runloop, which sometimes leads to reentering UIApplication delegate methods, which apparently is not thread-safe.
  3. Of cause introduction of splash screen is an obvious solution, but I was looking for a something more simple and elegant. I have a feeling it exist.
Alexander Vasenin
  • 11,437
  • 4
  • 42
  • 70
  • 3
    Instead of delay the return of `didFinishLaunchingWithOptions:`, I think showing a loading view which tells the user what the app is doing in front of main window is a better idea. – KudoCC Apr 03 '15 at 09:53
  • Possible problem with spinning run loop: http://stackoverflow.com/questions/26371462/weird-crash-when-launching-app-from-notification-center – Alexander Vasenin Apr 06 '15 at 14:09
  • Your solution can work with a small change in the approach. I suppose your loaded objects are part of the shared state in your application. You can simply add a `ready` flag that you set `true` when the load operation finishes. Other parts of the application can check this flag repeatedly (with an NSTimer) or you can use KVO or notifications to get notified of the completion. Trying to force synchronous operation is usually not a good idea when UI is also involved. – allprog Apr 09 '15 at 12:25

4 Answers4

2

While this technically works depending on what your setup is doing (for example web service calls) the application may never launch or launch with unexpected results.

The more user friendly method would be to add a "loading" view controller to your storyboard which would be your landing view. Perform your long running setup here while providing the user with information/status/whatever is appropriate, then push your original view controller.

NRimer
  • 284
  • 1
  • 3
2

Imho I think it's better to do your stuff in your main View Controller and show something to user like a custom spinner: in this way you can download your data and the user know what happens.

It's never a good idea take so long time to launch an app with a blank screen or just a launch screen with no info for the user.

Massimo Polimeni
  • 4,826
  • 4
  • 27
  • 54
2

Lagging the return of didFinishLaunchingWithOptions: is really a bad idea.This will give the bad impression of your app and its a bad design.

Instead you could land in the view controller and show a overlay view that shows the progress or activity you are doing when completed you could dismiss it.This is the right approach.

Here is a link to Third party library that eases showing/hiding the overlay view and setting some text on the view.

You can find so many libraries like this just google iOS HUD.HUD means Heads Up Display or you can design the custom thing yourself using UIAnimation easily.

Generally speaking,

I have to appreciate that code you have written in the sense you have stopped the current flow or run loop using While and NSRunloop.

May be you can alter your code little bit and use in some other place of your program to stop the current flow or runloop and wait for something.

NSDate *runloopUntill = [NSDate dateWithTimeIntervalSinceNow:0.1];
while (YourBoolFlag && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode runloopUntill])

The following apple documentation will give you deep insights in NSRunloop 1 2

Durai Amuthan.H
  • 31,670
  • 10
  • 160
  • 241
2

This is creative but a really bad idea. You should make your UI load in a failsafe state - in other words, assume the network is not available/slow, and show a view that won't explode if it doesn't get data before being rendered. Then you can safely load data on a background queue and either update the view or transition to the data-centric view once it is done loading.

 dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
        //Load your data here

        //Dispatch back to the main queue once your data is loaded.
        dispatch_async(dispatch_get_main_queue(), ^{
            //Update your UI here, or transition to the data centric view controller. The data loaded above is available at the time this block runs
        });
    });
RyanR
  • 7,728
  • 1
  • 25
  • 39