Runtime Loading and Initialization

Understand precisely what happens between a process launching and its first line of application code running: how the dynamic linker loads Objective-C images, how the runtime registers every class and category those images contain, and how the two startup hooks +load and +initialize differ in timing, ordering, inheritance, and thread-safety -- a distinction that causes real, hard-to-diagnose bugs when misunderstood.

Every Objective-C program appears, from the outside, to simply 'start' at main() with all of its classes already fully formed and ready to use. In reality, a substantial amount of work happens before that first line of application code ever runs, and a further, different kind of setup happens lazily, class by class, the first time each class is actually used. This lesson pulls apart that two-stage story in full: the one-time, eager work the runtime does for every Objective-C image as the process loads, centered on the +load method, and the lazy, per-class work triggered the first time a class is actually messaged, centered on +initialize. Confusing these two hooks -- using one where the other was intended -- is one of the most common sources of subtle Objective-C startup bugs, from crashes caused by code that isn't ready yet to setup logic that silently runs more than once. This lesson exists to make the distinction impossible to forget.

Objective-C Images and the Dynamic Linker

An 'image' in this context is not a picture -- it is the standard term for a loadable binary: the main executable itself, plus every dynamic library (framework or .dylib) linked into the process. A typical iOS or macOS app is made up of dozens of images: the app binary, Foundation, UIKit or AppKit, and any third-party frameworks it links against, each compiled and packaged separately but loaded together into one running process.

When a process launches, the operating system's dynamic linker (dyld on Apple platforms) is responsible for locating every image the executable depends on, mapping each one into the process's address space, and resolving the symbol references between them -- all before main() runs. This is a strictly lower-level step than anything the Objective-C runtime does; dyld doesn't know or care what a 'class' is, only what a mapped image and its exported symbols look like.

Once dyld has mapped an image into memory, it hands control to the Objective-C runtime for that image specifically, which is the point where the class-registration process described in the next section actually begins. This handoff happens once per image, as each one is loaded -- not once for the whole process -- which is exactly why an app's own images and every framework it links against each go through this same sequence independently.

• An 'image' is any loadable binary in the process: the main executable, plus every framework and dynamic library it links against. • dyld, the dynamic linker, maps every needed image into the process's address space and resolves symbols between them before main() runs. • dyld operates below the Objective-C runtime -- it knows about mapped memory and symbols, not about classes. • As each image is mapped, the Objective-C runtime takes over for that image specifically, beginning class registration.

Class Registration: How the Runtime Learns What Classes Exist

Every class and category an image contains is described, at compile time, by metadata the compiler emits directly into that image's binary -- data structures describing the class's name, its superclass, its instance variable layout, its method lists, and its protocol conformances, exactly the structures the Objective-C Class Anatomy lesson walks through from the inside. None of this metadata is 'alive' yet in any runtime sense the moment the image is merely mapped into memory; it is inert data sitting in the binary until the runtime processes it.

As part of loading each image, the Objective-C runtime walks that metadata and registers every class it describes: it builds the live, in-memory class objects the rest of the runtime (and your running code) will use, resolves each class's superclass pointer to the actual superclass object -- which may live in a different image entirely, such as your app's own class inheriting from a UIKit class -- and, when categories are present, splices each category's methods into its target class's method lists.

This registration step is what makes classes usable at all: before it happens for a given image, none of that image's classes exist yet as far as the runtime is concerned, no matter how completely their metadata is baked into the binary. This is also precisely the moment referenced in the Objective-C Runtime lesson's broader description of how the runtime turns static, compiled metadata into live, dynamic class objects -- this lesson picks up exactly where that one leaves the topic, focused specifically on the startup timeline.

• Class and category metadata is compiled directly into an image's binary -- inert data until the runtime processes it. • Registration builds live, in-memory class objects: resolving each class's superclass (possibly in a different image) and splicing category methods into their target classes. • A class does not exist, as far as the runtime is concerned, until its image has been registered -- regardless of how complete its compiled metadata is. • This is the same class-object machinery the Objective-C Class Anatomy and Objective-C Runtime lessons describe structurally -- this lesson covers exactly when it happens.

+load: Eager, Image-Load-Time Execution

+load is a class method the runtime calls automatically, directly after an image is loaded and its classes are registered -- well before main() runs for the app's own images, and immediately after loading for any image loaded later (a dynamically loaded framework, for instance). If a class implements +load, the runtime is guaranteed to call it, once, at this point; you never call +load yourself.

Two properties make +load fundamentally different from ordinary Objective-C methods. First, it is not looked up through the normal dynamic-dispatch machinery at all -- the runtime finds and calls each class's own +load implementation directly by address, which means it does not participate in overriding the way a normal method does and should never call super manually inside it (each class's own +load, if it has one, is simply called on its own). Second, and most important for how you reason about ordering, every +load in every category is called -- +load is the one place in Objective-C where a category's implementation does not replace the class's own implementation of the same method; both run, independently, exactly as the diagram accompanying this lesson does not show but the Categories lesson covers as this method's sole, deliberate exception to normal category-override behavior.

Ordering is deterministic, not random, even though it's rarely spelled out at the call site: a class's +load is guaranteed to run only after its superclass's +load has already run, so a subclass can safely assume any setup its superclass performs in +load has already happened. Categories' +load methods run after the class's own +load, but the relative order between images (which framework's classes get loaded and initialized first) is governed by dyld's own dependency-ordering, not by anything a class itself controls.

• +load runs automatically, once per class (and once per category), at image load time -- before main() for the app's own images. • You never call +load yourself, and it is looked up directly by the runtime, not through ordinary dynamic dispatch -- do not call super in it. • Both the class's own +load and every category's +load run -- +load is the one method category implementations do NOT override, they add to. • Ordering is deterministic: a superclass's +load always runs before its subclasses' +load.

+initialize: Lazy, First-Use Execution

+initialize is the opposite kind of hook: rather than running eagerly for every class at image load time, the runtime calls +initialize lazily, on a class the very first time that class (or any subclass of it) actually receives its first message -- allocating an instance, calling a class method, anything that triggers ordinary dynamic dispatch to that class for the first time. A class that is never used at all during a program's run may never have +initialize called on it.

Unlike +load, +initialize IS looked up through ordinary Objective-C method resolution, which means it is inherited exactly like any other class method: if a subclass does not implement its own +initialize, sending the first message to that subclass calls its superclass's +initialize instead, because that's simply what ordinary inherited-method lookup does. This single fact is the source of the most common +initialize bug, covered in detail below.

Because +initialize is inherited, a class hierarchy with several subclasses, none of which override +initialize themselves, can end up having the same inherited +initialize implementation invoked once for each distinct subclass the first time each one is used -- the runtime calls it once per class that is actually 'first used', and inheritance alone does not prevent the same physical method body from running multiple times across a hierarchy, once per class it's invoked on behalf of.

• +initialize runs lazily -- the first time a class (or an uninitialized subclass of it) receives its first message, not eagerly at image load time. • A class that's never used during a run may never have +initialize called on it at all. • +initialize IS looked up through ordinary inherited method dispatch -- a subclass without its own +initialize triggers its superclass's +initialize instead. • Because it's inherited, the same +initialize body can run once per distinct subclass that's first used, unless each class guards against this explicitly.

+load vs +initialize: The Critical Distinction

This is the single most important thing to internalize about Objective-C startup, and the diagram accompanying this lesson lays out the full sequence end to end: a process loads an image, the runtime registers that image's classes, +load runs for every class and category in that image, and then, only later, whenever a class is first actually used, +initialize runs for it.

The practical consequence is significant. Code inside +load runs extremely early -- potentially before other classes your code depends on have even been registered yet, and definitely before main() and any of an app's ordinary setup code has run -- so +load implementations must be self-contained and must not assume any other part of the app, or even unrelated parts of the same framework, are ready yet. Code inside +initialize, by contrast, runs lazily and only on demand, so it can safely assume the class itself is fully registered and can safely use other classes, since using them is exactly what triggered +initialize to run in the first place.

A second, easily overlooked distinction: +load is guaranteed to run exactly once, full stop, for the class and separately once for each category that implements it. +initialize, despite looking like a natural 'runs once' hook, only truly runs once PER CLASS if every class in the hierarchy that gets used implements its own guarded +initialize -- otherwise, as covered above and demonstrated in the mistake below, the same inherited implementation can run multiple times across a subclass hierarchy.

• +load: eager, at image-load time, before main(), for every class and category, unconditionally, exactly once each. • +initialize: lazy, on first actual use of a class, inherited, potentially re-triggered once per subclass unless explicitly guarded. • +load code must not depend on the rest of the app being ready yet; +initialize code safely can, because being used is what triggered it. • '+load always runs once' is a safe assumption; '+initialize always runs once' is NOT, unless you guard it yourself.

Thread-Safety During +load and Category Behavior

The runtime holds an internal lock for the duration of the class-loading process while +load methods are running, which the Objective-C Runtime lesson describes more generally as part of the runtime's own internal locking around class registration. The practical rule that falls out of this is strict: never perform blocking work, never try to acquire other locks, and never call code that might, even indirectly, try to message a class that hasn't been loaded yet from inside +load -- doing so risks deadlocking the app before it has even fully started, since the very lock your blocking code might be waiting on could be the same lock the runtime is holding to run +load in the first place.

+initialize, by contrast, is inherently thread-safe by construction: the runtime guarantees that a class's +initialize is called, at most, in a way that's serialized so that concurrent first-use from multiple threads doesn't race to run the same class's +initialize twice simultaneously -- you don't need to add your own locking around ordinary +initialize setup code for this reason.

The category behavior worth restating precisely, because it inverts the rule you'd expect from every other Objective-C method: an ordinary method defined in a category REPLACES the class's own implementation of the same method (with the order undefined if multiple categories define it), but +load is the deliberate, documented exception -- a category's +load does not replace the class's +load; the runtime calls both, independently, guaranteeing neither one silently disappears just because the other exists. This makes +load specifically useful for the kind of one-time framework or library setup that categories often exist to add, without any risk of a category on the same class accidentally suppressing that setup.

• The runtime holds an internal lock while running +load methods -- never block, lock, or trigger cross-class messaging from inside +load. • +initialize is called in a way that's safe under concurrent first-use from multiple threads -- no extra locking needed for ordinary setup inside it. • Ordinary category methods REPLACE the class's own method of the same name; +load is the deliberate exception -- both the class's and every category's +load run. • This makes +load specifically safe for one-time framework setup added via a category, without risk of the class's own +load being silently skipped.

Code Examples

This +load runs the moment the image containing AnalyticsKit is loaded and its classes are registered -- with no code anywhere calling it, and regardless of whether AnalyticsKit is ever actually used later in the program. The runtime calls it unconditionally, exactly once, for every class that implements it.

+ (void)load { Because +initialize is looked up through ordinary inherited dispatch, self inside it is the actual class that was first used -- which may be a subclass, not Logger itself. The if (self == [Logger class]) guard makes sure the one-time setup body only runs when Logger itself, specifically, is the class being initialized.

if (self == [Logger class]) { Common mistake: Omitting the self == [ThisClass class] guard -- see the dedicated mistakes entry below for the concrete failure this causes.

For any other method name, defining it again in a category would replace the class's own implementation, with only one version ever running. +load is the deliberate exception the runtime carves out: both the class's own +load and the category's +load are called, independently, which is exactly why +load is safe to rely on for framework or library setup added via a category.

// At image load time, BOTH lines are logged The runtime's own internal locking during the load process means +load is one of the few places in Objective-C where ordinary, otherwise-safe patterns like @synchronized or blocking I/O become genuinely dangerous -- the safest rule is to treat +load as a place for fast, self-contained, non-blocking registration only.

@synchronized (self) { Common mistake: Performing blocking work or acquiring additional locks inside +load, risking a deadlock with the runtime's own loading lock.

This ordering guarantee is what makes +load usable for layered setup across a class hierarchy: a subclass never needs to worry about running before the groundwork its superclass's +load lays down, even though neither +load calls the other or calls super explicitly.

// Shape +load // Circle +load

@implementation AnalyticsKit

+ (void)load {
    // Runs automatically at image load time, before main() runs for the
    // app's own images. Keep this self-contained -- do not assume any
    // other part of the app is ready yet.
    NSLog(@"AnalyticsKit image loaded");
}

@end
@implementation Logger

+ (void)initialize {
    // Guard against this running again for a subclass that doesn't
    // implement its own +initialize -- without this check, the SAME
    // body below would run once per subclass the first time each one
    // is used, because +initialize is inherited.
    if (self == [Logger class]) {
        NSLog(@"Logger one-time setup");
    }
}

@end
// Defined in Logger.m:
@implementation Logger
+ (void)load {
    NSLog(@"Logger's own +load ran");
}
@end

// Defined separately, in Logger+Networking.m:
@implementation Logger (Networking)
+ (void)load {
    NSLog(@"Logger+Networking category +load ran");
}
@end

// At image load time, BOTH lines are logged -- unlike an ordinary
// category method, which would silently replace the class's own
// implementation of the same method.
@implementation RiskyLoader

+ (void)load {
    // WRONG: the runtime holds an internal lock while +load methods run
    // across the whole loading image. Acquiring another lock, or doing
    // any blocking work, here risks deadlocking startup before main()
    // has even run.
    @synchronized (self) {
        [NSThread sleepForTimeInterval:2.0];  // never do this in +load
    }
}

@end
@implementation Shape
+ (void)load { NSLog(@"Shape +load"); }
@end

@implementation Circle : Shape
+ (void)load { NSLog(@"Circle +load"); }
@end

// Guaranteed output order at image load time:
//   Shape +load
//   Circle +load
// A subclass's +load is only ever called after its superclass's +load
// has already run -- Circle's +load can safely assume any setup Shape
// performed in its own +load has already happened.

Startup Sequence: Image Load Through First Use

The full sequence, top to bottom: an image is loaded, the runtime registers every class and category it contains, +load runs immediately and unconditionally for all of them, and then, only later -- whenever a class is actually first used -- +initialize runs lazily for that specific class.

Process Loads Image
        ↓
Runtime Registers Classes
        ↓
+load
        ↓
Class First Used
        ↓
+initialize

Where This Distinction Actually Matters

Libraries and frameworks that need to register themselves globally -- swizzling a method for logging, registering a custom class with a plugin system, or installing a crash handler -- reach for +load specifically because it's guaranteed to run early and unconditionally, without requiring any application code to remember to call a setup function. Application-level one-time setup that depends on a class actually being used -- lazily creating a shared resource the first time a particular manager class is touched, or validating environment configuration only for features that end up being exercised -- belongs in a correctly guarded +initialize instead, precisely because it defers work until it's actually needed rather than paying the cost unconditionally at every app launch. Confusing the two is the classic mistake: putting slow, app-dependent setup in +load causes startup slowdowns or crashes from code that isn't ready yet, while putting must-always-run-once registration in +initialize risks it never running at all if that particular class happens not to be used during a given run.

@implementation Logger + (void)initialize { NSLog(@"Setting up shared state"); sharedFormatter = [[NSDateFormatter alloc] init]; } @end // FileLogger and NetworkLogger both subclass Logger and implement no // +initialize of their own. Using FileLogger for the first time, then // NetworkLogger for the first time, prints "Setting up shared state" // TWICE and creates the formatter twice.

@implementation Logger + (void)initialize { if (self == [Logger class]) { NSLog(@"Setting up shared state"); sharedFormatter = [[NSDateFormatter alloc] init]; } } @end // Now, using FileLogger or NetworkLogger for the first time still // triggers +initialize (inherited), but the guard ensures the setup // body only actually runs once, when Logger itself is the class.

Because +initialize is an ordinary inherited method, the runtime calls it once for each distinct class that is first used -- and self inside the inherited body is whichever class actually triggered it, not necessarily the class that defined it. Without the if (self == [Logger class]) guard, every subclass that doesn't override +initialize causes the same setup code to run again, duplicating shared state exactly the way a well-behaved singleton or one-time initializer must never do.

At what point in a program's lifecycle does +load run, relative to main()? A. Only after main() has started running, the first time the class is used B. At image load time, before main() runs for the app's own images C. Only when explicitly called by application code D. Immediately before the process exits, as cleanup

B. At image load time, before main() runs for the app's own images — +load runs automatically as part of image loading -- for an app's own classes, this happens before main() ever runs, and for a dynamically loaded framework's classes, immediately after that framework is loaded.

Why does a subclass that doesn't implement its own +initialize risk running its superclass's +initialize body more than once across a class hierarchy? A. It doesn't -- +initialize is guaranteed to run exactly once per hierarchy no matter what B. +initialize is looked up through ordinary inherited method dispatch, so each distinct subclass that's first used triggers the inherited implementation again, with self set to that subclass C. The runtime randomly re-runs +initialize as part of memory management D. +load and +initialize are actually the same method under the hood

B. +initialize is looked up through ordinary inherited method dispatch, so each distinct subclass that's first used triggers the inherited implementation again, with self set to that subclass — Unlike +load, +initialize is an ordinary class method resolved through normal inheritance -- so the first use of any subclass that doesn't override it triggers the inherited body again, which is exactly why the self == [ThisClass class] guard is necessary.

How does a category's +load method behave differently from an ordinary category method with the same name as one on the class? A. It behaves identically -- the category's version replaces the class's version B. It never runs at all -- only the class's own +load runs C. Both the class's own +load and the category's +load run independently -- +load is the deliberate exception to the usual category-override rule D. The category's +load runs first and prevents the class's +load from being registered

C. Both the class's own +load and the category's +load run independently -- +load is the deliberate exception to the usual category-override rule — Every other category method replaces the class's own implementation of the same name, but +load is special-cased by the runtime: it calls both the class's own +load and every category's +load, rather than letting one silently suppress the other.

Why is it dangerous to perform blocking work or acquire additional locks inside +load? A. It isn't dangerous -- +load runs on a dedicated background thread with no restrictions B. The runtime holds an internal lock while running +load methods during image loading, so blocking or locking there risks deadlocking the app before it has fully started C. +load methods time out automatically after one second regardless of what they do D. Blocking in +load causes a compile-time error

B. The runtime holds an internal lock while running +load methods during image loading, so blocking or locking there risks deadlocking the app before it has fully started — Because the runtime's own loading process holds a lock while +load methods run, any blocking work or additional lock acquisition inside +load risks contending with that same lock, which can deadlock startup entirely.

What guarantees the ordering between a superclass's +load and its subclass's +load? A. There is no guarantee -- the order is effectively random B. The subclass's +load always runs first, so it can prepare state for the superclass C. A class's +load is guaranteed to run only after its superclass's +load has already run D. Ordering depends entirely on alphabetical class name order

C. A class's +load is guaranteed to run only after its superclass's +load has already run — The runtime guarantees a superclass's +load runs before any of its subclasses' +load methods, which is exactly what allows a subclass's +load to safely assume setup performed by its superclass has already completed.

A class ImageCache defines +load to register itself with a global cache registry, and separately defines +initialize to lazily create its internal NSCache instance. A subclass, DiskImageCache, overrides neither method. Explain, in order, everything that happens automatically at (1) image load time and (2) the first time DiskImageCache is used, including whether ImageCache's +load-registered entry and +initialize-created NSCache are shared with DiskImageCache, and what would go wrong if +initialize's body didn't check self == [ImageCache class].

At image load time: ImageCache's own +load runs once, registering ImageCache (not DiskImageCache, which has no +load of its own to run) with the global registry -- DiskImageCache is never separately registered by this mechanism since it doesn't implement +load itself. The first time DiskImageCache is actually used, the runtime looks up +initialize via ordinary inherited dispatch, finds no override on DiskImageCache, and calls ImageCache's +initialize with self equal to DiskImageCache. If that body isn't guarded with if (self == [ImageCache class]), it runs its NSCache-creation logic again for DiskImageCache, producing a second NSCache instance rather than reusing the one created when ImageCache itself was first used -- exactly the same duplicated-setup bug the Logger mistake above demonstrates.

+load is called directly per class/category regardless of inheritance and is never inherited in the ordinary dispatch sense; +initialize IS ordinary inherited dispatch, so DiskImageCache's first use triggers ImageCache's +initialize body with self set to DiskImageCache.