Weak Reference is a reference to an object that does not increase its retain count in ARC. According to Apple Swift Language Guide, 2026, weak references are declared with the weak keyword and are always optional. When the object is deallocated, all weak references to it are automatically set to nil, preventing dangling pointers and making weak references a safe mechanism for breaking retain cycles.
Key Takeaways
weak keyword before var; type is always optional (?)Weak Reference is a non-owning reference to an object in ARC (Automatic Reference Counting). Unlike a strong reference, which increases the object’s retain count and guarantees its lifetime, a weak reference allows the object to be deallocated even if it is still being referenced. After deallocation, the weak reference is automatically set to nil — this is called zeroing weak.
Zeroing weak is a key feature of the Swift and Objective-C runtime. When an object’s reference count reaches zero and the object is deallocated, the runtime iterates through all weak references to this object (stored in a special weak table) and sets them to nil. This ensures that accessing freed memory (use-after-free) is impossible through weak references — any read returns nil.
According to Apple WWDC 2012 Session 406, zeroing weak references eliminated an entire class of crash bugs related to dangling pointers, which were common in manual memory management (MRR). In MRR, weak references only existed as __unsafe_unretained — they did not zero out, and accessing a deallocated object resulted in EXC_BAD_ACCESS.
Let’s look at the syntax for declaring weak references in both Apple ecosystem languages. Despite the shared runtime, the syntax differs, but the semantics are identical.
In Swift, weak references are declared with the weak keyword before var. The type must always be optional (Type?), since the reference can become nil at any time. Constants (let) cannot be weak — only variables.
class ViewController: UIViewController {
// weak properties: only var, only optional
weak var delegate: ViewControllerDelegate?
weak var parentView: UIView?
weak var completionHandler: ((Bool) -> Void)? // ⚠️ closures do not store weak
// ⬆️ Error: weak can only be applied to class types, not closures
}
Important: weak is only applicable to class instances (class types), AnyObject, and protocols inherited from AnyObject. Struct, enum, and closures cannot be weak — they are value types and do not participate in ARC.
In Objective-C, weak properties are declared using the __weak attribute or the weak modifier in property declarations:
// Objective-C: weak property
@interface MyViewController : UIViewController
@property (weak, nonatomic) id<MyDelegate> delegate;
@end
// Local weak variable
__weak MyObject *weakRef = someStrongObject;
The Objective-C runtime also provides zeroing weak, but additionally blocks the use of weak with C structures and some Core Foundation objects. For these, __unsafe_unretained is used — without zeroing.
Weak references are not a universal solution, but a tool for specific scenarios. Using weak everywhere leads to unnecessary complexity and hurts readability. Let’s look at the correct usage scenarios.
Delegates — the primary scenario for weak. The owning object (e.g., UITableView) holds a strong reference to itself, while the delegate (UIViewController) should not own the table. Apple SDK guarantees that all delegates and dataSources are weak. For your own protocols, always use weak var delegate.
When a child object needs to reference its parent (e.g., ChildViewController accessing a coordinator), use a weak reference. The parent owns the child (strong), the child observes the parent (weak) — retain cycle is eliminated.
Capture list [weak self] — the standard way to avoid retain cycles in closures stored as class properties. If self may be deallocated before the closure completes, weak self is mandatory.
| Scenario | Weak | Strong |
|---|---|---|
| Delegate | ✅ Always weak | ❌ Retain cycle |
| Parent → Child | ❌ Not needed (parent should own) | ✅ Strong |
| Child → Parent | ✅ Weak | ❌ Retain cycle |
| Async callback | ✅ [weak self] | ❌ Retain cycle risk |
| Strong coupling (owned) | ❌ unowned | ✅ Strong |
General rule: if object A owns B (A → B strong), then B → A should be weak or unowned. The direction of strong references should always be from owner to subordinate.
Both weak and unowned do not increase the retain count, but differ in behavior after object deallocation. The choice between them is a matter of lifetime guarantees.
Weak: automatically becomes nil, type is always optional, requires unwrapping before use. Safe — accessing nil does not cause a crash.
Unowned: does not become nil, type is non-optional. If the object is deallocated, an unowned reference becomes a dangling pointer — accessing it causes a runtime crash. Unowned assumes the object lives at least as long as the referencing side.
Choose weak if: the object may be deallocated at any time (delegate after screen dismissal), you do not control the object’s lifetime, or you are unsure about guarantees. Weak is the universal safe choice.
Choose unowned if: the object is guaranteed not to be deallocated before the referencing object (e.g., Customer → CreditCard, where the card does not exist without the customer). Unowned provides a non-optional API without unwrapping, which is more convenient in code.
class Order {
let id: Int
var items: [Item] = []
init(id: Int) { self.id = id }
// Strong relationship: Order owns Item
func addItem(name: String) {
let item = Item(name: name, order: self)
items.append(item)
}
}
class Item {
let name: String
unowned let order: Order // ✅ unowned — Item does not live without Order
init(name: String, order: Order) {
self.name = name
self.order = order
}
}
// Example with weak: delegate without lifetime guarantee
protocol NetworkServiceDelegate: AnyObject {
func didReceiveResponse(data: Data)
}
class NetworkService {
weak var delegate: NetworkServiceDelegate? // ✅ weak — delegate may go away
}
In the example, Item uses unowned because an order item cannot exist without the order itself — the lifetime guarantee is ironclad. NetworkService uses weak because the delegate (e.g., ViewController) may be dismissed and deallocated at any time.
Weak references are a powerful tool, but they have limitations that are important to understand for correct usage in iOS development.
Weak references are slower than strong: on each access, the runtime checks whether the object has been deallocated (lookup in the weak table). In the vast majority of scenarios, the difference is imperceptible, but in hot loops with millions of accesses, weak can become a bottleneck. For high-load scenarios, use strong and reorganize the architecture.
Struct, enum, tuple — value types that do not participate in ARC. Attempting to declare a weak struct results in a compilation error. To store a weak reference to a value type, use a wrapper in a class type or a closure.
Zeroing weak is thread-safe: if an object is deallocated on one thread, the weak reference is zeroed on all threads atomically. However, the window between reading a weak reference and dereferencing it can lead to a race condition — the object is deallocated between obtaining the weak reference and using it. Solution: strong capture of the weak reference into a local variable.
// Race condition with weak in multithreading
func performAsync() {
weak var weakSelf = self
queue.async {
// ⚠️ weakSelf may be nil between check and use
if weakSelf != nil {
weakSelf!.doSomething() // CRASH if becomes nil
}
}
}
// ✅ Fix: strong capture during use
func performAsyncSafe() {
queue.async { [weak self] in
guard let strongSelf = self else { return }
strongSelf.doSomething() // strongSelf — local strong reference
}
}
In the safe version, weak self is captured, then immediately unwrapped into a local strong variable strongSelf. If self is still alive, it will remain alive for the duration of the block. If not, guard triggers and the code does not execute. This idiom is the standard pattern for asynchronous closures in Swift.
IBOutlet in Interface Builder should be weak because the view hierarchy already holds a strong reference to the subview. Duplicating a strong reference in the controller does not create a retain cycle but is redundant. A weak reference to an outlet is Apple’s recommendation, although many developers use strong for code simplicity.
Frequently Asked Questions
No, weak can only point to an existing object or nil. When creating a new object, you first obtain a strong reference (via an initializer), and only then can you assign a weak reference. A weak nil at the start is a normal state.
Weak is based on ARC, which only manages reference types (classes). Value types (struct, enum) are copied on assignment and do not have a retain count. For weak relationships with value types, use closures or wrappers in a class with a weak property.
Each access to a weak reference performs a lookup in the runtime table. In a loop with millions of iterations, this can be 2–5 times slower than a strong reference. For hot paths, copy weak into a local strong variable before the loop.
When all strong references to the object are lost — at the end of scope, when a property is reassigned, or when a screen is dismissed. In a multithreaded environment, this can happen between two lines of code. Always check weak references with guard let or if let.
Semantically identical: both provide zeroing weak. Differences: Swift requires an optional type and var, Objective-C uses a property modifier. Objective-C also supports __unsafe_unretained — a weak reference without zeroing (risk of dangling pointer).
Summary
weak var + optional type; only class types and AnyObject protocolsWe will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also