A difference with the AtomicLazyReference approach is that it won't stop concurrent calls to the initializer: it's a race where the first initializer reaching the finish line sets the value. Good for some cases, but could be problematic if the initializer has side effects or consume resources.
You'll have to make other threads wait for the initializer to finish if you want the same behavior as static let. Here's the same example using Mutex to get rid of concurrent initialization:
class Image {
let _histogram = AtomicLazyReference<Histogram>()
let _histogramInitMutex = Mutex<Void>(())
// This is safe to call concurrently from multiple threads.
var atomicLazyHistogram: Histogram {
if let histogram = _histogram.load() { return histogram }
// Code here may run concurrently on multiple threads
return _histogramInitMutex.withLock { _ in
// No more concurrency here while under the lock
// 1. check again if value was set while waiting for the lock
if let histogram = _histogram.load() { return histogram }
// 2. run initializer
let histogram = ...
return _histogram.storeIfNil(histogram)
}
}
}
Edit: note that this implementation will deadlock if it calls itself recursively through the initializer.