Some confusion on StackOverflow led to a massive string of comments. This is a question that comes up often, so here is some google fodder.

In Objective-C, a class can implement +initialize . This method will be invoked the first time the class is touched, prior to any other methods (other than +load ).

The documentation says:

The runtime sends initialize to each class in a program exactly one time just before the class, or any class that inherits from it, is sent its first message from within the program.

Which is exactly true. But your +initialize methods can still be executed more than once!

Specifically, if a subclass does not implement +initialize but its superclass does, then that superclass’s +initialize will be invoked once per non-implementing subclass and once for itself.

An example (Foundation Tool, Garbage Collected):

@interface Abstract: NSObject @end @implementation Abstract + (void) initialize { NSLog(@"Initializing %@", self); } + (void) load { NSLog(@"Loading"); } @end @interface Sub : Abstract @end @implementation Sub @end int main (int argc, const char * argv[]) { [Sub class]; return 0; }

This will output:

ArgyBargy[3720:903] Loading ArgyBargy[3720:903] Initializing Abstract ArgyBargy[3720:903] Initializing Sub

Which is why most +initialize methods are implemented as:

@implementation MyClass + (void) initialize { if (self == [MyClass class]) { // ... do +init stuff here ... } } ... @end

Now, categories can seriously screw things up (as usual). Namely, if you implement +initialize in a category, it will override the classes +initialize . However, a category provided +load will not; both the category’s and the class’s +load methods will be invoked.

If you were to add the following category to the Sub/Abstract/NSObject example above:

@interface Abstract(Cat) @end @implementation Abstract(Cat) + (void) load { NSLog(@"Category +load"); } + (void) initialize { NSLog(@"Category +initialize %@", self); } @end

The program will spew:

ArgyBargy[3919:903] Loading ArgyBargy[3919:903] Category +load ArgyBargy[3919:903] Category +initialize Abstract ArgyBargy[3919:903] Category +initialize Sub

Keep in mind, as well, that the runtime sends +initialize “in a thread-safe manner”. That implies that there is a lock involved somewhere within which then also implies that you better not block on a lock in your +initialize because whoever is supposed to unlock the lock might end up blocking on +initialize s lock.

Or, to put it more bluntly, do not do any heavy lifting in +initialize . Keep it super simple & fast.

For me, +initialize is to be used only as a method of last resort. Well, 2nd to last. Last resort is a constructor attributed function (or +load ).In the comments, James says:

+initialize is a great place to do heavy lifting. … Your objection about not performing any locks in +initialize is non-sensical. The runtime won’t allow any other other threads to send a message to the class until the +initialize message returns.

That claim is wrong.

While it is a great place to, say, initialize very simple global state– string constants, the date, etc… — +initialize is a horrible place to do any kind of heavy lifting for several reasons.

Most critically, it is impossible to know what order the classes will be +initialize ‘d and said order will change across system configurations and, even, software updates. This leads to a need to do some kind of exclusion locking or something such that the initialization code paths are only followed once. But, more often than not, “heavy lifting” in the +initialize will trigger other classes to be initialized.

End result? A mess of interleaved locks whose complexity quickly grows out of control.

Here is a dead simple example of a +initialize deadlock:

#import <Foundation/Foundation.h> @interface Bleep : NSObject @end @interface Blorp : NSObject @end static NSLock *initializationLock; @implementation Bleep + (void) initialize { NSLog(@"%s", class_getName(self)); [initializationLock lock]; NSLog(@"%@", [Blorp new]); [initializationLock unlock]; } @end @implementation Blorp + (void) initialize { NSLog(@"%s", class_getName(self)); [initializationLock lock]; NSLog(@"%@", [Bleep new]); [initializationLock unlock]; } @end int main (int argc, const char * argv[]) { initializationLock = [NSLock new]; [Blorp new]; return 0; }

Conveniently, the runtime detects the deadlock for us:

209 BleepBlorb[33953:903] Blorp 213 BleepBlorb[33953:903] Bleep 214 BleepBlorb[33953:903] *** -[NSLock lock]: deadlock (<NSLock: 0x20000f340> '(null)') 214 BleepBlorb[33953:903] *** Break on _NSLockError() to debug.

And the backtrace at the point of deadlock is interesting beyond the obvious failure:

#0 semaphore_wait_signal_trap () #1 pthread_mutex_lock () #2 -[NSLock lock] () #3 +[Bleep initialize] (self=0x100001120, _cmd=0x7fff8790a148) at /tmp/BleepBlorb/BleepBlorb.m:15 #4 _class_initialize () #5 prepareForMethodLookup () #6 lookUpMethod () #7 objc_msgSend () #8 +[Blorp initialize] (self=0x1000010d0, _cmd=0x7fff8790a148) at /tmp/BleepBlorb/BleepBlorb.m:26 #9 _class_initialize () #10 prepareForMethodLookup () #11 lookUpMethod () #12 objc_msgSend () #13 main (argc=1, argv=0x7fff5fbff7e0) at /tmp/BleepBlorb/BleepBlorb.m:37

In particular, it shows that the runtime, in and of itself, can trigger class initialization as a part of method lookup, thus implying even less control over initialization order than one might assume.

Now, of course, this is a contrived example. However, this particular pattern of dysfunctionality is quite easy to grow into as the complexity of initialization increases. Worse, it can be equally as easy to end up in a situation where the deadlock only occurs some of the time; only on some app launches on some subset of customers machines.

This isn’t just conjecture. I have intermittently spent quite a few hours at a time debugging this exact kind of deadlock across various applications (that shall remain nameless– popular apps, they were).

For the record, +load is an even worse time to do heavy lifting. The runtime environment is in an extremely non-deterministic state at that time.

: link_pages issince version 2.1.0! Use wp_link_pages() instead. inon line