Swift vs. Objective-C Global Variable
Published:
In WWDC 2024’s Swift 6 Migration, Ben from Apple’s Swift team mentioned that there is a big difference for the global variable (mutatble) between Objective-C and Swift.
Question
Between Swift and Objective-C global variables, which one will be in the risk of a data race? And which one is thread-safe?
Anwser
Objective-C global variable is not thread-safe and it’s prune to data race. Swift global variable is thread-safe but it’s also prune to data race.
Why They Are Both Prune To Data Race?
Objective-C Global Variables
Creation Time
- Static Initialization: In Objective-C, global variables are typically initialized at program startup, before the
main
function is executed. This is known as static initialization. - Compile Time: The compiler allocates memory for global variables, and their initialization occurs as the binary is loaded into memory by the runtime.
Data Race Risk
- Data Race Risk: Objective-C global variables can be prone to data races because they are not inherently thread-safe. If multiple threads attempt to read and write to a global variable simultaneously, it can lead to inconsistent or incorrect states.
- Synchronization Required: To prevent data races, explicit synchronization mechanisms like locks or GCD (Grand Central Dispatch) are needed when accessing global variables across multiple threads.
Swift Global Variables
Creation Time
- Lazy Initialization: In Swift, global variables are lazily initialized. This means that the global variable is not created until it is first accessed. This is managed by a dispatch_once-like mechanism internally, ensuring the variable is initialized only once.
- Deferred Initialization: This lazy initialization helps in reducing the initial memory footprint and avoids unnecessary initialization if the global variable is never used.
Data Race Risk
- Thread-Safety with Lazy Initialization: Swift’s lazy initialization of global variables is thread-safe. The runtime ensures that even if multiple threads attempt to access the global variable simultaneously, the variable will be initialized only once.
- Data Race Risk Reduced: Although the initialization process is thread-safe, the use of global variables in Swift can still lead to data races if the variable is mutable and accessed concurrently without proper synchronization.
- Synchronization Needed for Mutable Variables: For mutable global variables, developers must ensure thread-safe access by using synchronization mechanisms such as dispatch queues or locks to avoid data races.
Example Comparison
Objective-C
// Objective-C global variable
int globalVariable = 0;
// Function to increment the global variable
void incrementGlobalVariable() {
globalVariable++;
}
Swift
// Swift global variable
var globalVariable: Int = 0
// Function to increment the global variable
func incrementGlobalVariable() {
globalVariable += 1
}
In both cases, if incrementGlobalVariable
is called from multiple threads simultaneously, there is a risk of a data race. However, in Swift, the initial creation of globalVariable is thread-safe due to its lazy initialization, while in Objective-C, this is not a consideration since the variable is initialized at program startup.
Summary
- Creation Time:
- Objective-C: Global variables are initialized at program startup.
- Swift: Global variables are lazily initialized upon first access, ensuring thread-safe initialization. This means Swift will have faster app launch time in this perspective.
- Data Race Risk:
- Objective-C: Global variables can lead to data races if accessed concurrently without synchronization.
- Swift: While the lazy initialization is thread-safe, mutable global variables still require explicit synchronization to avoid data races during concurrent access.
Fix Swift 6 Concurrency Warning
If all the callers are from main thread / main actor, we can add annotation
// Swift global variable
@MainActor var globalVariable: Int = 0
[last resort] We can also let consumer be cautious by adding
// Swift global variable
nonisolated(unsafe) var globalVariable: Int = 0