Search code examples
objective-cinitializationstartup

Objective-C: How to force a call to `+initialize` at startup rather than later when the class happens to used for the first time?


Problem

For certain classes, I would like to explicitly call the +initialize method when my program starts, rather than allowing the runtime system to call it implicitly at some nondeterministic point later when the class happens to first be used. Problem is, this isn't recommended.

Most of my classes have little to no work to do in initialization, so I can just let the runtime system do its thing for those, but at least one of my classes requires as much as 1 second to initialize on older devices, and I don't want things to stutter later when the program is up and running. (A good example of this would be sound effects — I don't want sudden delay the first time I try to play a sound.)

What are some ways to do this initialization at startup-time?

Attempted solutions

What I've done in the past is call the +initialize method manually from main.c, and made sure that every +initialize method has a bool initialized variable wrapped in a @synchronized block to prevent accidental double-initialization. But now Xcode is warning me that +initialize would be called twice. No surprise there, but I don't like ignoring warnings, so I'd rather fix the problem.

My next attempt (earlier today) was to define a +preinitialize function that I call directly instead +initialize, and to make sure I call +preinitialize implicitly inside of +initialize in case it is not called explicitly at startup. But the problem here is that something inside +preinitialize is causing +initialize to be called implicitly by the runtime system, which leads me to think that this is a very unwise approach.

So let's say I wanted to keep the actual initialization code inside +initialize (where it's really intended to be) and just write a tiny dummy method called +preinitialize that forces +initialize to be called implicitly by the runtime system somehow? Is there a standard approach to this? In a unit test, I wrote...

+ (void) preinitialize
{
  id dummy = [self alloc];
  NSLog(@"Preinitialized: %i", !!dummy);
}

...but in the debugger, I did not observe +initialize being called prior to +alloc, indicating that +initialize was not called implicitly by the runtime system inside of +preinitialize.

Edit

I found a really simple solution, and posted it as an answer.


Solution

  • Answering my own question here. It turns out that the solution is embarrassingly simple.

    I had been operating under the mistaken belief that +initialize would not be called until the first instance method in a class is invoked. This is not so. It is called before the first instance method or class method is invoked (other than +load, of course).

    So the solution is simply to cause +initialize to be invoked implicitly. There are multiple ways to do this. Two are discussed below.

    Option 1 (simple and direct, but unclear)

    In startup code, simply call some method (e.g., +class) of the class you want to initialize at startup, and discard the return value:

    (void)[MyClass class];
    

    This is guaranteed by the Objective-C runtime system to call [MyClass initialize] implicitly if it has not yet been called.

    Option 2 (less direct, but clearer)

    Create a +preinitialize method with an empty body:

    + (void) preinitialize
    {
      // Simply by calling this function at startup, an implicit call to
      // +initialize is generated.
    }
    

    Calling this function at startup implicitly invokes +initialize:

    [MyClass preinitialize];  // Implicitly invokes +initialize.
    

    This +preinitialize method serves no purpose other than to document the intention. Thus, it plays well with +initialize and +deinitialize and is fairly self-evident in the calling code. I write a +deinitialize method for every class I write that has an +initialize method. +deinitialize is called from the shutdown code; +initialize is called implicitly via +preinitialize in the startup code. Super simple. Sometimes I also write a +reinitialize method, but the need for this is rare.

    I am now using this approach for all my class initializers. Instead of calling [MyClass initialize] in the start up code, I am now calling [MyClass preinitialize]. It's working great, and the call stack shown in the debugger confirms that +initialize is being called exactly at the intended time and fully deterministically.