Writing effective Swift code understanding how the memory works in iOS [part 1]
Memory Footprint and common memory issues
Overview
In order to write effective Swift code is very important to understand how memory works behind the scenes. In this article, we are going to explore how the memory is being used by the app and which are the common issue that makes an app consumes a lot of memory.
Why my app is using all that memory? 🤯
Before answering this question we have to understand what are the Memory Page. These are given us by the system, they can hold multiple objects and some objects can actually span multiple pages.
e.g.: UILabel stored in a part of the memory page
So imagine we have allocated an array of 2000 integers and the system gives us 6 memory pages now these all are “clean”. Once we start writing inside the array these pages start becoming dirty and iOS dedicates RAM to store its content.
The metric of the memory used is determined by the number of pages * the page size.
Memory in use = Number of pages * Page size
A typical app footprint has a dirty, compressed and clean segment of memory.
The compressed segment is because iOS does not have a traditional file swap system instead it uses a memory compressor that was introduced with iOS 7. The memory compressor squeezes the unaccessed pages so that it can reduce the amount of memory used and it will restore it once the app will request access to that space again.
Memory Footprint = Dirty Memory + Compressed Memory
How reduce the Memory Footprint?
To reduce the memory footprint, first, we need to address the issues. The most common issues are Leaks and Heap size issues.
Memory Leaks in Swift and how to prevent them
Keeping a strong reference between two reference types will end up creating a memory leak. A memory leak is some memory that the system allocates and will never dispose of since the process will not be killed. A common way objects leak in Swift is via retain cycles, retain is the name of the instruction that the compiler introduces at compile-time and cycle is because you are actually creating a cycle between these two reference types(A->B and B->A). This is very dangerous as it can bring the app to be unresponsive and worst-case scenario crash because of wasting too much memory.
So we really don’t want them inside our app.
In the code example above, we can notice how we can create a memory leak. Why this? Because you are creating a cycle of strong references(Person->Pet and Pet->Person) and the counter of these will never be set to 0, so these instances will never be disposed of. Objects in Swift are managed by ARC(Automatic Reference Counting).
How to avoid retain-cycle?
Ideally, the best option is never to design your class in a way that this loop is created, but in case we cannot go for this option(UIKit is one of these) we can use weak and unowned to break such a cycle, using one of this will basically inform ARC to do not increase the reference count for that reference.
Heap size issues
Not just leaks can make our app consume a lot of memory but also heap size issues. Heap is the place where dynamically allocated objects are stored. Allocating more objects on the heap increases our app memory footprint.
To reduce the Heap size, we can:
- remove unused allocations
- shrink overly large allocations
- deallocate memory once we are finished with it and wait to allocate memory until we need it(A very good example is unloading images when they are not visible anymore and loading them back when we need to display them).
These good practices will significantly reduce our app memory footprint.
Fragmentation
A particular type of heap size issue is fragmentation. Memory Pages are the smallest indivisible unity of memory for your process and writing anything to a page makes the whole Page dirty(even if most of it is unused). Fragmentation occurs when the process has dirty pages that are not 100% utilized. The best way to reduce fragmentation is to allocate contiguous objects with similar lifetimes. Some fragmentation is inevitable and so as long as our dirty and compressed fragmentation size is under 25% is acceptable.
Conclusion
In this article, we have looked more closely at how the memory works in iOS and which are the common issues that make our app consume a lot of memory. In the second part of this article, we will go through this issue, looking at how to investigate and resolve them.
I hope you enjoyed the reading and please let me know what you think about this? Sound off in the comments
Thanks
👏 Don’t forget to tap that clap button