Greetings, traveler!
Swift 6.2 brings a subtle but powerful language enhancement: you can now declare properties using weak let
. This complements the existing weak var
, offering a new way to create immutable yet non-owning references to class instances.
At first glance, weak let
might seem like a contradiction — how can something be both weak and immutable? But this combination unlocks new opportunities for safer architecture and memory management, particularly in concurrency and Sendable
contexts.
What is weak let
?
With weak let
, you can create a reference that:
- Does not retain the referenced object (like all weak references),
- Cannot be reassigned after initialization (unlike
weak var
).
This means the reference can still become nil
if the object is deallocated, but you can’t point it to something else later.
Example
Consider a scenario where you have a Downloader
class that communicates progress to its delegate
. Traditionally, the delegate must be a weak var
to avoid a retain cycle — especially when the delegate is a view controller that owns the downloader.
With Swift 6.2, we can now use weak let
for delegates if they are set once and never change, making the relationship safer and compatible with Sendable
.
protocol DownloaderDelegate: AnyObject {
func downloadDidUpdate(progress: Double)
}
final class Downloader: Sendable {
weak let delegate: DownloaderDelegate?
init(delegate: DownloaderDelegate?) {
self.delegate = delegate
}
func simulateDownload() {
// Simulated update
delegate?.downloadDidUpdate(progress: 0.5)
}
}
var controller: ViewController? = ViewController()
let downloader = Downloader(delegate: controller)
downloader.simulateDownload()
controller = nil
downloader.simulateDownload() // delegate is now nil, no message sent
downloader.delegate = AnotherViewController() // ❌ compiler error
And here’s how we’d use it:
final class ViewController: DownloaderDelegate {
func downloadDidUpdate(progress: Double) {
print("Progress: \(progress)")
}
}
var controller: ViewController? = ViewController()
let downloader = Downloader(delegate: controller)
downloader.simulateDownload()
controller = nil
downloader.simulateDownload() // delegate is now nil, no message sent
downloader.delegate = AnotherViewController() // ❌ compiler error
Why Is This Useful?
Initially, it may not be obvious why weak let
is needed. If you can’t reassign it, why not just use weak var
?
Here’s why it matters:
- Sendable compliance:
weak var
can’t be markedSendable
, butweak let
can. That makesweak let
especially useful in Swift’s structured concurrency. - Immutable design: You can now hold weak references in value types or concurrency-safe contexts without compromising immutability.
- Cleaner ownership graphs:
weak let
helps reduce strong reference cycles while preserving thread safety and intent.
When to Reach for weak let
- In actor-isolated or
Sendable
types that still need weak references. - When working with UI references that must not retain their targets, like views pointing to their view models.
- For creating snapshot-like relationships that should automatically break when the source disappears.
Conclusion
weak let
may not be a feature you reach for daily, but it’s a perfect example of Swift evolving toward more expressive, safer code — especially in modern, concurrent architectures.
If you enjoyed this article, please feel free to follow me on my social media:
It might be interesting:
- Leveraging Enums for Flexible Button Styling in SwiftUI
- How to access UIHostingController from a SwiftUI View
- Simplifying Data Access in SwiftUI with @dynamicMemberLookup
- Three Practical Tools for Managing References and Detecting Memory Leaks in Swift
- Paging with Peek: Three Ways to Implement Paginated Scroll in SwiftUI