Memory management has been always an issue in the era before ARC, many of the current iOS devs. didn't have to get their hands dirty with retain, alloc and release messages, but still there are many use cases where memory leaks might occur, leading to an increased memory foot print or dangling pointers and eventually crashes, a common scenario leaks often occur is within "Blocks" or as called in swift "Closures", where I will explain it and give some hints of how to avoid it in the next lines.
Memory Management Basics
Memory in iOS is managed through using concept of "Reference Counting" where memory chunks (corresponding to a class instance) are reserved to the app as long as there is at least one strong reference to it (e.g. through a property or a var), this is managed through ARC which holds the info about how many the number of references for this instance and increases or decreases the count accordingly and once the count reaches 0, the ARC frees up this memory chunk and make it available for use again.
lets check this practically through the classical example of employee and company, so create a new playground and create two classes "employee" and "company"
Now create an instance of Employee class, this makes the retainCount of this instance = 1
Create an instance Company class and add employeeA in its employeesArray, now employeeA Instance has 2 strong references to it and companyX instance has 1 strong reference.
now add a property to the Employee class , which would identify the company this employee belongs to
and assign companyX to employeeA, this will make companyX has a retainCount of 2
this is an example of a very common retain cycle, try now to wrap them in a do block (notice how the init and deinit logs are handy) and it will show that the instances are not deallocated, but only initialised.
in order to resolve this cycle, just add weak or unowned beside the company property, personally I tend to use weak more often, since it is safer, you can read more about the weak and unowned here.
now notice the console output
Wooho, now everything works as expected and the cycle is resolved, hopefully this refreshed your memory about how memory management works generally in CocoaTouch, now lets jump to the main topic today which is about Closures.
Closures or Blocks
Blocks or Closures are awesome but unfortunately they are very good candidates for memory leaks, as there are 2 issues which normally could be faced :
1- RetainCycle: through having strong reference from the closure to the objects it encloses has a reference to the block already
e.g. add a lazy var block property to the employee class
then call block() from employeeA instance
and notice from the console that
doesn't get called anymore which means that there is a retain cycle here.
2- Memory Occupancy/ Wrong Behaviour: when an async call (e.g. GCD ) returns after a delay and another action already happened in progress, causing a wrong behaviour.
As you can notice in the code snippet above, if you some tracking might not be valid anymore.
captureList the famous solution for most of the closure problem is to have a of the objects it encloses and references it, for example
this will solve the retain cycle problem.
However, this was a never-ending discussion with my former team mate for multiple months as he had this philosophy of weakfying every self to be safe before executing the block then make a strong reference to it inside the block, my issue here was that I don't believe in useless extra code, specially that many times you need to have strong references for some calls (e.g tracking or updating shared data) or a UIViewAnimationBlock. so I was against it using as a rule of thumb.
Avoiding memory leaks
Use structs and not classes unless you really need referencing, after all it avoids memory reference so less to worry about leaks.
Use weak when two classes reference each other to break a retain cycle.
When a closure is a class property, make sure to capture [weak self] or any other object which will be used inside it , if it might cause a retain cycle. However to avoid crashes from having the captured object de-allocated , make sure to guard it before using it inside the block.
Add to the init() and deinit()method with a meaningful message as in the example above, as it is very useful when debugging.
Make it a habit to alway use the amazing VisualMemoryGraphDebugger in Xcode to detect retain cycles, before making a commit.
BONUS: Stack & Heap
Normally It shouldn't be bothered about where the memory allocated physically, however understanding it, will also be helpful when writing code to make better decisions of which data structure to use in case dealing with performance sensitive one.
so briefly speaking Stack is the segment of the memory where local variables are stored (e.g. ones used in a function) and data structures of known size (e.g. Int) at compile-time, they are fast to access and relatively
cheap compared to Heap, in swift the value types (struct and enum) are stored on a Stack.
Heap is the segment of the memory where dynamic allocation happens at run-time, reference types (e.g class) are allocated and deallocated on Heap according to the referenceCount as mentioned earlier.