First, my question: How do you manage your iOS Run-Loop?
Next my reason: I've been researching this with a variety of prototypes (v. early stage development) and have found a number of perplexing issues.
So has anyone found a magic bullet around these issues? Does anyone have a killer run-loop architecture that's kick-ass on this platform? At the moment it looks like I have to pick the lesser of the evils.
For my own iOS projects, I use the classic approach (create a window .nib, create a class inheriting EAGLView
, add EAGLView
to a view in a view controller which is placed in its own .nib).
At work, I took a slightly different approach inspired by SDL, which you can inspect in our opensourced library, APRIL. Main goal of APRIL is support for as many platforms as possible, while retaining simplicity (window and input management only) and being clear about licensing issues and free to use. Our developers want to write apps on one platform (Windows, Mac or Linux, according to tastes and desires) and then the code is handed over to me to adapt for other platforms.
In the approach we use in APRIL, you don't create any .nibs, and upon calling UIApplicationMain
, you specify the delegate class as its fourth argument. Main code of game remains absolutely the same for each platform, and only platform-specific stuff is #ifdef
'd into the code, or abstracted in a helper library.
In the app delegate you create the view controller and the window:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// create a window.
// early creation so Default.png can be displayed while we're waiting for
// game initialization
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// viewcontroller will automatically add imageview
viewController = [[AprilViewController alloc] initWithWindow:window];
[viewController loadView];
// set window color
[window setBackgroundColor:[UIColor blackColor]];
// display the window
[window makeKeyAndVisible];
// thanks to Kyle Poole for this trick
// also used in latest SDL
// quote:
// KP: using a selector gets around the "failed to launch application in time" if the startup code takes too long
// This is easy to see if running with Valgrind
[self performSelector:@selector(runMain:) withObject:nil afterDelay:0.2f];
}
Notice how we delay launching by 0.2? That's why I mention image view above. During those 0.2 seconds, we'd have blank screen displayed immediately after Default.png, and extra delay is introduced before control is transferred to runMain:, which releases control to the main app:
- (void)runMain:(id)sender
{
// thanks to Kyle Poole for this trick
char *argv[] = {"april_ios"};
int status = april_RealMain (1, argv); //gArgc, gArgv);
#pragma unused(status)
}
So, now the control is never transferred back to UIApplication's actual main loop. You then create your own main loop.
void iOSWindow::enterMainLoop()
{
while (mRunning)
{
// parse UIKit events
doEvents();
handleDisplayAndUpdate();
}
}
void iOSWindow::doEvents()
{
SInt32 result;
do {
result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, TRUE);
} while(result == kCFRunLoopRunHandledSource);
}
(On a side note, view controller is used, of course, to simplify rotation of UI to match device orientation.)
Both of these approaches use CADisplayLink
if supported by the OS. I have not noticed any issues with either of the methods, although my private projects are primarily accelerometer based. I suspect APRIL approach might make some of the problems go away, too.