How to Gracefully Use OC +load Timing in Swift
In the world of Objective-C, the +load method is a special existence. It executes when a class is added to the Objective-C runtime, even before the main function. This makes +load a powerful tool in specific scenarios, such as performing some global initializations, registration logic, or Method Swizzling. Developers can leverage this timing to "silently" complete some necessary work at the very early stages of application startup. However, as Swift becomes the mainstream language for Apple ecosystem development, we find that Swift itself does not directly provide a mechanism completely equivalent to Objective-C's +load. This poses some challenges for developers accustomed to the convenience of +load or when migrating old code that relies on +load functionality to Swift. How can we "gracefully" regain this capability in Swift? Introducing! Rhea - Unlocking +load Timing in Swift This is where our protagonist—Rhea(https://github.com/reers/Rhea) —comes into play! Rhea is a lightweight open-source Swift library that cleverly utilizes Swift's latest Macro feature, allowing us to concisely and intuitively register code blocks in Swift code that need to be executed at specific timings (including simulating Objective-C +load timing). Imagine, you just need to write: import Rhea #load { // This code will execute at a timing similar to OC +load print("Swift code executed via Rhea at load timing!") // Perform your early initialization, module registration, etc., here } // Rhea also supports other preset timings, such as premain and appDidFinishLaunching #premain { print("Swift code executed via Rhea at premain timing!") } #appDidFinishLaunching { context in print("Swift code executed via Rhea at appDidFinishLaunching timing!") if let launchOptions = context as? [UIApplication.LaunchOptionsKey: Any] { // Handle launch options } } You can even define custom event timings: extension RheaEvent { static let myCustomEvent: RheaEvent = "myCustomEvent" } #rhea(time: .myCustomEvent, priority: .normal) { _ in print("Custom event myCustomEvent was triggered!") } // Trigger the custom event at the appropriate time Rhea.trigger(.myCustomEvent) You can also use the async parameter for callback on a background thread: #rhea(time: .homePageDidAppear, async: true, func: { context in // This will be called back on a background thread print("~~~~ homepageDidAppear") }) Even in XCode 16 and above, you can nest it within types, just like the load method in the OC era: class ViewController: UIViewController { #load { print("~~~~ load nested in main") } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) } } Through the #load macro provided by Rhea (as well as #premain, #appDidFinishLaunching, etc.), you can very "Swiftly" arrange the logic that needs to be executed in the early stages of application startup. Rhea handles the underlying implementation details, allowing your codebase to maintain a pure Swift style while enjoying the convenience similar to +load. Moreover, it only requires import RheaTime without any other configuration or initialization. This undoubtedly provides an elegant solution for scenarios requiring modular automatic registration, early configuration, and so on. Sounds great, doesn't it? Rhea makes goals that originally needed to be achieved through +load in Objective-C, or through some other complex techniques, readily accessible in Swift. Wait, Is Using load Extensively in Swift Really "Graceful"? Just as we are immersed in this "gracefulness" brought by Rhea, an important question emerges: should we really promote and use this +load timing code execution method extensively in Swift projects? The answer is likely no. Although Rhea provides the powerful capability to simulate +load timing in Swift, abusing this mechanism, even if implemented in Swift through such a clever library, is not truly the "graceful" way. On the contrary, it might bring some non-negligible negative impacts to your application, the most significant of which is slowing down the app's cold launch speed. +load and Application Startup Time Objective-C's +load method (and similar timings simulated by Rhea) has a notable characteristic: it is called before the main function executes. When an application starts, the dynamic linker (dyld) loads all dependent libraries and classes. During this process, if a class implements the +load method, that method will be executed. This means: Blocking the startup path: +load methods are executed synchronously. If the code in a +load method takes too long to execute, it will directly block the subsequent startup process, extending the time from when the user taps the App icon to when they see the first screen. Cumulative effect: If a large number of classes in the project use +load (or register #load code blocks via

In the world of Objective-C, the +load
method is a special existence. It executes when a class is added to the Objective-C runtime, even before the main
function. This makes +load
a powerful tool in specific scenarios, such as performing some global initializations, registration logic, or Method Swizzling. Developers can leverage this timing to "silently" complete some necessary work at the very early stages of application startup.
However, as Swift becomes the mainstream language for Apple ecosystem development, we find that Swift itself does not directly provide a mechanism completely equivalent to Objective-C's +load
. This poses some challenges for developers accustomed to the convenience of +load
or when migrating old code that relies on +load
functionality to Swift. How can we "gracefully" regain this capability in Swift?
Introducing! Rhea - Unlocking +load
Timing in Swift
This is where our protagonist—Rhea(https://github.com/reers/Rhea) —comes into play!
Rhea is a lightweight open-source Swift library that cleverly utilizes Swift's latest Macro feature, allowing us to concisely and intuitively register code blocks in Swift code that need to be executed at specific timings (including simulating Objective-C +load
timing).
Imagine, you just need to write:
import Rhea
#load {
// This code will execute at a timing similar to OC +load
print("Swift code executed via Rhea at load timing!")
// Perform your early initialization, module registration, etc., here
}
// Rhea also supports other preset timings, such as premain and appDidFinishLaunching
#premain {
print("Swift code executed via Rhea at premain timing!")
}
#appDidFinishLaunching { context in
print("Swift code executed via Rhea at appDidFinishLaunching timing!")
if let launchOptions = context as? [UIApplication.LaunchOptionsKey: Any] {
// Handle launch options
}
}
You can even define custom event timings:
extension RheaEvent {
static let myCustomEvent: RheaEvent = "myCustomEvent"
}
#rhea(time: .myCustomEvent, priority: .normal) { _ in
print("Custom event myCustomEvent was triggered!")
}
// Trigger the custom event at the appropriate time
Rhea.trigger(.myCustomEvent)
You can also use the async
parameter for callback on a background thread:
#rhea(time: .homePageDidAppear, async: true, func: { context in
// This will be called back on a background thread
print("~~~~ homepageDidAppear")
})
Even in XCode 16 and above, you can nest it within types, just like the load method in the OC era:
class ViewController: UIViewController {
#load {
print("~~~~ load nested in main")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
}
Through the #load
macro provided by Rhea (as well as #premain
, #appDidFinishLaunching
, etc.), you can very "Swiftly" arrange the logic that needs to be executed in the early stages of application startup. Rhea handles the underlying implementation details, allowing your codebase to maintain a pure Swift style while enjoying the convenience similar to +load. Moreover, it only requires import RheaTime
without any other configuration or initialization. This undoubtedly provides an elegant solution for scenarios requiring modular automatic registration, early configuration, and so on.
Sounds great, doesn't it? Rhea makes goals that originally needed to be achieved through +load in Objective-C, or through some other complex techniques, readily accessible in Swift.
Wait, Is Using load Extensively in Swift Really "Graceful"?
Just as we are immersed in this "gracefulness" brought by Rhea, an important question emerges: should we really promote and use this +load timing code execution method extensively in Swift projects?
The answer is likely no.
Although Rhea provides the powerful capability to simulate +load timing in Swift, abusing this mechanism, even if implemented in Swift through such a clever library, is not truly the "graceful" way. On the contrary, it might bring some non-negligible negative impacts to your application, the most significant of which is slowing down the app's cold launch speed.
+load and Application Startup Time
Objective-C's +load method (and similar timings simulated by Rhea) has a notable characteristic: it is called before the main function executes. When an application starts, the dynamic linker (dyld) loads all dependent libraries and classes. During this process, if a class implements the +load method, that method will be executed.
This means:
- Blocking the startup path: +load methods are executed synchronously. If the code in a +load method takes too long to execute, it will directly block the subsequent startup process, extending the time from when the user taps the App icon to when they see the first screen.
- Cumulative effect: If a large number of classes in the project use +load (or register #load code blocks via Rhea), each will contribute a little bit to the startup time. When these times accumulate, the impact on cold launch performance becomes very significant. Users are very sensitive to slow-launching Apps.
Apple has also been emphasizing the importance of optimizing application startup time. In WWDC presentations, they have clearly pointed out that the time before the main function executes (Pre-main time) is one of the key stages affecting startup performance. This stage includes dylib loading, rebase/binding, ObjC setup, and initializers (including +load methods and C++ static constructors, etc.).
Simply put, the more you do at the +load timing, the slower your application will start.
Other Potential Issues
Besides the direct impact on startup performance, over-reliance on +load timing might also bring:
- Environmental limitations: When +load executes, the complete application environment may not yet be ready. For example, some UIKit classes may not be available or may not behave as expected. Although Rhea itself works in a Swift environment, executing complex logic too early still requires caution.
- Debugging difficulty: Code that executes very early in the startup chain, if problems occur, can sometimes be harder to trace and debug than code that runs after the application is normally running.
- Risk of order dependency: Although the Objective-C Runtime has rules for the calling order of +load (superclass before subclass, class before category), in complex projects, implicit execution order dependencies can still cause problems. Rhea provides priority settings, which can alleviate this problem to some extent, but still require careful management by developers.
Conclusion: Use with Caution, Not Abuse
Rhea is a cleverly designed library that indeed provides Swift developers with an effective means to simulate Objective-C +load and other early timing execution of code in specific scenarios. For some lightweight initialization or registration tasks that truly need to be performed very early and cannot be achieved through other better methods, Rhea can be a good choice.
However, this does not mean we should make the +load pattern (even if implemented in Swift via Rhea) a common option in our regular arsenal.
True "gracefulness" is not just about being able to implement a certain function with some trick, but more about understanding the costs behind this function and making wise trade-offs.
For most initialization and configuration tasks, Swift provides more recommended and language-idiomatic ways:
- Lazy Initialization: For objects that do not need to be used immediately, postpone their creation and initialization time.
- AppDelegate / SceneDelegate: Perform application-level and UI-level configurations in
application(_:didFinishLaunchingWithOptions:)
or related SceneDelegate methods. This is the most common and recommended initialization timing. - Dependency Injection: Manage and pass dependencies through constructor injection, property injection, etc., rather than relying on global state or early automatic registration.
- Static properties and global constants: For simple constants or type properties, their initialization is already efficient and safe enough.
Summary
In conclusion, the Rhea library opens a window for you to explore +load timing in Swift. But please remember, powerful tools need to be used prudently. Do not sacrifice your application's startup performance and the user's initial experience for the sake of some "magical" automatic execution. In most cases, choosing an initialization scheme that is more in line with Swift's design philosophy and friendlier to startup performance is the truly "graceful" way.