Strong Reference is a standard memory management mechanism where an object remains in memory as long as at least one active reference points to it. Unlike weak references, a strong reference increments the object’s reference count and prevents its automatic deallocation. According to Apple Developer Documentation, ARC automatically manages the lifetime of objects in Swift and Objective-C. Understanding how strong references work is critical for preventing memory leaks and cyclic dependencies in mobile applications.
Key Takeaways
Strong Reference is a type of reference to an object that prevents its destruction by the garbage collector or memory management system. As long as at least one strong reference to the object exists, its memory is not freed. This is the basic mechanism underlying ARC in Swift and Objective-C as well as garbage collection in Java and Kotlin.
The concept of strong references is fundamental to all languages with automatic memory management. In systems with ARC, each strong reference increments the object’s reference count. When the count drops to zero, the object is immediately deallocated. In Java and Kotlin with garbage collection, a strong reference ensures the object is reachable and will not be collected by the GC.
According to WWDC 2021, about 35% of memory leaks in iOS applications are related to incorrect use of strong references and retain cycles. In Android development, leaks through implicit strong references in closures and callbacks are the second most common cause of memory issues after Context Leak.
To work effectively with memory, you need to understand the difference between strong, weak, and unowned references and choose the right reference type based on ownership and object lifetime.
Before ARC, developers manually called retain and release for each object, leading to numerous errors. ARC, introduced by Apple in 2011 with LLVM 3.0, automated this process by analyzing the ownership graph at compile time. The compiler itself inserts retain, release, and autorelease calls where needed.
According to the Clang Static Analyzer, the introduction of ARC reduced memory-related bugs in iOS applications by 70%. For developers, this means memory management has become safer, but at the same time, it has become necessary to understand how strong references work under the hood — to avoid retain cycles.
In Kotlin and Java, the garbage collector plays the role of ARC, but the principle of strong references remains the same: GC Roots are entry points through which objects are held by strong references. As long as an object is reachable through a chain of strong references from a GC Root, it will not be collected.
ARC (Automatic Reference Counting) works by counting references for each object in the heap. When a new strong reference to an object is created, the counter increments (retain). When the reference is destroyed or overwritten, the counter decrements (release). When the counter reaches zero, the object is immediately removed from memory.
Consider a Swift example. When a class instance is created, ARC allocates memory and sets the retain count to 1. Each new assignment to another variable increments the counter. When the variable goes out of scope, the counter decrements:
class ProfileViewController {
var nameLabel: String?
var avatarImage: UIImage?
func loadProfile() {
// retain count = 1 for new instance
let user = User(name: "Ivan")
// retain count = 2 after assigning nameLabel
nameLabel = user.name
// exit method — user goes out of scope, retain count = 1
}
}
In this code, ARC ensures that the User object remains in memory as long as at least one strong reference points to it. When the loadProfile function finishes, the local user variable is destroyed, but nameLabel still holds the object. Memory will only be freed when nameLabel ceases to exist or is overwritten.
In Kotlin, similar behavior is provided through GC Roots. As long as a traceable chain of strong references from a garbage collector root (e.g., a static field or an active thread) exists, the object remains in memory. The difference is that the GC does not free memory instantly — it happens asynchronously after reachability analysis.
In ARC, deallocation occurs synchronously when the counter reaches zero. In Swift and Objective-C, you know exactly when the object will be removed. In Kotlin and Java, the deallocation moment is unpredictable, but this is compensated by a more flexible scheme for detecting cyclic dependencies at the garbage collector level.
Retain cycle is a situation where two or more objects have mutual strong references to each other. As a result, their retain count never drops to zero, and memory is never freed, even after the objects are no longer needed by the application.
A classic example: a parent view controller holds a child object with a strong reference, and the child in turn holds the parent with a strong reference. This is typical in situations with delegates, closures, and nested lambda expressions. According to Instruments Leaks, retain cycles account for up to 60% of all memory leaks in applications using ARC.
class ParentViewController: UIViewController {
var child: ChildViewController?
func setupChild() {
child = ChildViewController()
// retain cycle: parent holds child, child holds parent via closure
child?.onEvent = {
self.handleEvent()
}
}
func handleEvent() {}
}
The problem here is that the onEvent closure captures self (ParentViewController) with a strong reference, and ParentViewController itself holds child with a strong reference. Both objects will never be freed. The solution is to use weak self in the closure to break the cycle.
In Kotlin, similar cycles occur when lambdas capture external objects. The JVM garbage collector can eventually detect such cycles, but only if the objects are unreachable from GC Roots. If the cycle is tied to an active thread or UI context, the leak persists for the entire application lifetime.
Understanding the difference between reference types is key to safe memory management. Strong Reference increments the retain count. Weak Reference does not increment the retain count and automatically becomes nil when the object is deallocated. Unowned Reference also does not increment the retain count but is not zeroed — accessing it after deallocation causes a crash.
| Reference Type | Retain Count | Safety | When to Use |
|---|---|---|---|
| Strong | +1 | Safe (default) | Object ownership, parent → child relationship |
| Weak | Does not change | Auto-zeroing (safe) | Delegates, callbacks, reverse references |
| Unowned | Does not change | Crash risk on late access | When the object is guaranteed to outlive the owner |
The choice of reference type is dictated by the ownership relationship. If object B is part of A and cannot exist without it — use Strong. If B can exist independently and references A for notifications — use Weak. Unowned is rarely used — only when the child object’s lifetime strictly does not exceed the parent’s lifetime.
Apple Developer Documentation recommends: by default, use strong for all ownership relationships. If you need to avoid a retain cycle — determine which reference should be weak. Usually, this is the reverse reference in the hierarchy (child → parent). In Kotlin, a similar role is played by WeakReference from java.lang.ref, which is used for caches and observer patterns.
Detecting retain cycles is the first step. The second is properly eliminating them. The primary tool for breaking strong reference cycles is replacing one of the references with weak or unowned. In languages with garbage collection, WeakReference with manual null checking before each access is additionally used.
In Swift and Objective-C, the most common fix is adding [weak self] to closures. This ensures that the closure does not hold the object after it is deallocated. In Kotlin, WeakReference wrappers or explicit reference clearing in onDestroy are used for similar purposes.
class NetworkService {
func fetchData(completion: @escaping (Data?) -> Void) {
// capture via weak self — retain cycle eliminated
URLSession.shared.dataTask(
with: URL(string: "https://api.example.com")!
) { [weak self] data, response, error in
guard let self else { return }
completion(data)
}.resume()
}
}
In this example, [weak self] ensures that NetworkService is not held by the closure after it is no longer needed. If self is deallocated before the request completes — guard let self else { return } exits the closure without calling completion.
For retain cycle diagnostics, use Instruments Leaks for iOS or Android Profiler + LeakCanary for Android. These tools show the exact retention graph and indicate which strong reference is preventing object deallocation. Regular memory profiling should be part of any mobile project’s CI/CD pipeline.
Swift and Kotlin use fundamentally different memory management mechanisms, but the concept of strong references exists in both. Swift uses ARC with synchronous deallocation at retain count = 0. Kotlin uses a tracing GC that asynchronously cleans up unreachable objects.
| Parameter | Swift (ARC) | Kotlin (JVM GC) |
|---|---|---|
| Mechanism | Reference counting (retain count) | Reachability tracing (GC Roots) |
| Deallocation | Synchronous (when counter reaches zero) | Asynchronous (by GC cycle) |
| Retain cycle | Not detected automatically | GC may detect, but not immediately |
| Weak ref | weak (auto-zeroing) | WeakReference (manual checking) |
The main practical difference: in Swift, a retain cycle is a guaranteed leak. In Kotlin, the GC can break the cycle if the objects are unreachable from the root, but the lifetime of leaked objects remains unpredictable. Therefore, in both languages, the best strategy is to avoid strong reference cycles at the design stage.
For Swift, use weak in delegate patterns and closures. For Kotlin, use WeakReference or Lifecycle-aware components that automatically clear references when the owner is destroyed. In both approaches, the goal is the same — eliminate strong references where they create an unbreakable retention chain.
Frequently Asked Questions
Strong Reference increments the object’s retain count and prevents its deallocation as long as the reference exists. Weak Reference does not change the retain count and automatically becomes nil when the object is removed from memory. Strong references are used for ownership, weak references for reverse connections and delegates.
Retain cycle is a mutual lock where two objects hold each other with strong references. Their retain count never drops to zero, memory is not freed. This leads to memory leaks: objects remain in the heap forever, the application consumes more and more resources and eventually crashes with OutOfMemory.
Use Instruments Leaks from Xcode — run profiling with the Leaks template, execute a scenario in the app, and check the leak indicators. For precise diagnostics, switch to the Cycles & Roots tab — it shows the graph of mutual strong references that form an unbreakable cycle.
Unowned should be used when the child object’s lifetime is guaranteed not to exceed the parent’s lifetime — for example, when binding an object to a strictly defined scope. If in doubt, use Weak, as accessing a deallocated unowned reference causes an application crash.
Indirectly — yes. Each retain and release in ARC is an atomic operation with overhead. With a large number of objects in cycles, this can affect performance. However, the main problem is not ARC’s speed, but memory leaks due to an incorrectly chosen reference type.
Summary
We 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