views:

221

answers:

2

Hi everyone,

I'm currently kicking off a background thread to do some REST queries in my app delegate's didFinishLaunchingWithOptions. This thread creates some objects and populates the model as the rest of the app continues to load (because I don't block, and didFinishLaunchingWithOptions returns YES). I also put up a loading UIViewController 'on top' of the main view that I tear down after the background initialization is complete.

My problem is that I need to notify the first view (call it the Home view) that the model is ready, and that it should populate itself. The trick is that the background download could have finished before Home.viewDidAppear is called, or any of the other Home.initX methods.

I'm having difficulty synchronizing all of this and I've thought about it long enough that it feels like I'm barking up the wrong tree.

Are there any patterns here for this sort of thing? I'm sure other apps start by performing lengthy operations with loading screens :)

Thanks!

+1  A: 

I usually have a Factory class that’s responsible for wiring all the objects together. The Factory is created by the application delegate in the applicationDidFinishLaunching:

- (void) applicationDidFinishLaunching: (UIApplication*) app {
     // Creates all the objects that are needed to wire
     // the application controllers. Can take long. We are
     // currently displaying the splash screen, so that we
     // can afford to block for a moment.
     factory = [[Factory alloc] init];
     // Now that we have the building blocks, we can wire
     // the home screen and start the application.
     home = [[factory wireHomeScreen] retain];
     [window addSubview:home.view];
     [window makeKeyAndVisible];
}

Now if the Factory creation takes long, I simply wait under the splash screen or put up another view that displays spinner until everything is ready. I guess you could use this very scheme if you can perform the initialization synchronously:

@implementation Factory

- (id) init {
    [super init];
    // Takes long, performs the network I/O.
    someDataSource = [[DataSource alloc] init…];
    return self;
}

- (id) wireHomeScreen {
    // Data source already loaded or failed to load.
    HomeScreen *home = [[HomeScreen alloc] init…];
    [home setDataSource:someDataSource];
    return [home autorelease];
}

@end

With a bit of luck there’s just a single long operation in your startup routine, so that you won’t lose anything by serializing the init.

If you want to perform the data source init in background, you can display some introductory screen that will cue the home screen once the data has been loaded:

- (void) applicationDidFinishLaunching: (UIApplication*) app
{
     // Create the basic building blocks to wire controllers.
     // Will not load the data from network, not yet.
     factory = [[Factory alloc] init];
     // Display something while the data are being loaded.
     IntroScreen *intro = [[IntroScreen alloc] init];
     // Main screen, will get displayed once the data are loaded.
     home = [[factory wireHomeScreen] retain];
     // The intro screen has to know what do display next.
     [intro setNextScreen:home];
     // Start loading data and then notify the intro screen
     // that we are done loading and the show can begin.
     [factory.someDataSource startLoadingAndNotify:intro];
     [window addSubview:intro.view];
     [window makeKeyAndVisible];
}

Now when the data source has finished loading the data, it will tell the intro screen to cue the real content (home screen, in this case). This is just a rough sketch (for example the memory management might be different in the real case), but in principle it should work fine.

zoul
Thanks for the quick response! So what you're saying is to do all the init synchronously in `applicationDidFinishLaunching` because the app can't 'really' init until the model is prepared? It also sounds like you're creating and wiring the views by hand. I've been working hard to keep all of that in IB. What is the state of the views during `applicationDidFinishLaunching` or is it unspecified?
Rob S.
You can load the controllers and views just as you see fit. The Factory can call `initWithNibName:bundle:` to load the controllers and views from NIBs. The same goes for init during startup: if it makes sense, you can do it synchronously. If not, you can display some intro screen and do it in the background – I’ll add an example to the answer.
zoul
Thanks again for the additional detail. It sounds like I'll need to stop relying on the views automatically being loaded from the NIB? (re: "The Factory can call initWithNibName:bundle:") since that's the core of my problem - not being able to predictably rely on the creation/state of the views (the views are all loaded by a `UITabBarController`).
Rob S.
I’d have to see your NIB to make a clear picture. If you’re wiring too many things together in a single NIB it might be good for you to split the interface into separate NIBs and wire things together in code, like I do with my Factory. You’ll get cleaner NIBs, better separation for testing purposes and better control over initialization. But as I said, I’d have to see your NIB to be sure I’m not talking nonsense.
zoul
I think I'll split it as you suggest. Thanks for the detailed attention, it's much appreciated!
Rob S.
+1  A: 

I ran into a similar issue a while back writing a REST application. It wasn't at the home screen but the general idea was the same. Issues with synchronizing call backs with NSURLRequests and so on. I found an example code from Apple that doesn't really solve this problem but illustrates how Apple solved it. Here's the link...

http://developer.apple.com/iphone/library/samplecode/XMLPerformance/Introduction/Intro.html

They do block the thread, so maybe it's not the solution you are looking for. In short, they use...

-(void) get {

    finished = NO;
    ... do threading stuff ...

    do {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    } while (!finished);
}

Just make sure your thread calls set finished to YES at some point.

It's not a pattern by any means, but I found it very helpful if you are using multiple threads in your application.

Staros