As per Apple, a memory leak is:
Memory that was allocated at some point, but was never released and is no longer referenced by your app. Since there are no references to it, there’s now no way to release it and the memory can’t be used again. So a memory leak occurs when a content remains in memory even after its life cycle has ended.
Why do we need to take care of Leaks?
- Memory leaks increase the memory footprint of the app, and when it reaches a certain threshold the OS triggers a memory warning. If that memory warning is not handled, the app would be force-killed, which is an OOM (Out of memory) crash
- They pave the way for poor UX
Automatic Reference Counting
Like other high-level programming languages, Swift has graced us with Automatic Reference Counting. ARC is a built-in process that automatically finds unused objects and deletes them to free up memory. In most cases, ARC does its job well. However, leaks still happen in iOS by accident.
A retain cycle prevents an object from being deallocated even after its creator has been deallocated. This happens when two or more objects hold strong references to each other.
So Server and Client have strong references to each other. The Client reference count cannot be 0 until the Server is released, and at the same time, the Server class reference counter cannot be 0 until the Client is released. Both of these are retaining reference to each other, making it a retain cycle.
Detect Memory leaks with Allocations and Leaks Instruments
In a real-time application, we can’t find memory leaks just by looking at the code, we need some tools. There are various tools available to detect leaks such as Memory graph debugger or Instruments. Here we will see how to use the Leaks instrument
- From the toolbar of XCode, choose Product > Profile > Leaks to start a new instrument profile for tracking memory leaks.
- Now, press the red button on the top left in the panel to start recording. When the app automatically launches, we press on the navigate button that presents our leaking view controller
- We simply pop it using the navigation item back button
- Finally, we observe the effect in the leaks instrument and memory graph
Breaking a retain cycle to prevent memory leak
Retain cycles can be broken if one reference in the cycle is weak or unowned. The cycle must exist because it is required, but all the associations cannot be strong. One of them must be weak or unowned.
It's all about reference type: so lets understand how by using appropriate reference type, we can avoid memory leaks
- Strong reference is a non-optional type and default declaration of a property to an instance. They increase the reference counter by 1. It is safe to use strong references when the hierarchy relationships of objects are linear. When a hierarchy of strong references flow from parent to child, then it's always ok to use strong references
- The weak reference is an optional type, which means weak reference will set to nil once the instance it refers to frees from memory. Weak references don’t increase the reference counter.
- Unowned reference is a non-optional type, it never will be set to nil and always have some value. It is essential to understand and remember that an unowned reference should always point to a class instance. If you attempt to access an unowned reference that no longer points to a class instance, a runtime error is thrown and your application is terminated
Using weak to break retain cycle
Example 1: By making an instance of Client as weak we can prevent memory leak. Now server has a weak reference to the client while client still has a strong reference to Server. When Client is released, server will automatically be released by ARC.
Example 2: Retain cycles in closures
Closures are reference types and therefore can result in a memory leak if you reference any object that has a reference to the closure itself. See the example of a retain cycle below, the body of the closure is referencing the class.
This can be resolved by using a capture list. You can mark the type of reference to weak inside the body closure, by marking the self reference to weak the retain cycle is broken.
Using Unowned to break retain cycle
Example 1: Suppose we have two classes Customer and Car. A customer can exist without a car, but a car will not exist without a customer, i.e. it can be assumed that a car will always have a customer. So, they should have the following relationship:
Example 2: Avoiding Retain cycle in observer using unowned
Do all the blocks require weak or unowned self?
No, Not all blocks require weak or unowned self to prevent retain cycles. Below are few example which don't create any retain cycle
Example 1: UIView animation
UIView animation blocks run only once and are then deallocated. This means even strong references in animation blocks will be released when the block runs
Example 2: Dispatch queue closures
In above example, even though completion block is escaping, which is owned by DispatchQueue and it captures self inside, there is no ownership semantics between self and DispatchQueue instance thus avoiding a retain cycle in other direction.
Example 3: Dismiss blocks
This is not a retain cycle. First off, completion closure is non-escaping. Which means it is not retained by the UIViewController superclass which is responsible for calling it after UIViewController is successfully dismissed
Example 4: Computed properties
Same way, computed property blocks also don't create any retain cycle.
If you are unsure if a property should be declared as weak or unowned, then default to weak. A weak reference can't cause harm. An unowned reference can when used incorrectly. Some points to keep in mind while deciding it:
- Make sure delegates are marked weak
- Self captured in closures, to be made weak or unowned
- If a child object has a parent object as its property, try to use weak or unowned
General practices that we follow in Halodoc to avoid Memory Leaks
- We have a strong code style defined for our project. Code reviews really help us.
- We use SwiftLint. It is a great tool that enforces you to adhere to a code style and keep rules.
- We use MLeaksFinder. It is an open source tool. It shows an alert whenever it detects a leak with a class name.
As outlined above, its clearly evident that ARC does an amazing job managing memory for our iOS application. All we have to do as developers is to be aware of strong references between classes , between a class and a protocol , and inside closures by declaring weak or unowned variables in those cases. I hope you find this information useful and keep these tips in mind regularly as you develop! Thanks!
Some great references to dig deep into Memory Leaks
Scalability, reliability, and maintainability are the three pillars that govern what we build at Halodoc Tech. We are actively looking for engineers at all levels and if solving hard problems with challenging requirements is your forte, please reach out to us with your resumé at firstname.lastname@example.org.
Halodoc is the number 1 all-around Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 3500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allow patients to book a doctor appointment inside our application. We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, Astra, Temasek, and many more. We recently closed our Series C round and In total have raised around $180 million for our mission. Our team works tirelessly to make sure that we create the best healthcare solution personalized for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.