Arc
ARC
Automatic Reference Counting
Intro
Value and Reference type comes into picture as always. Number one culprit is the closures which are reference type by nature will retain the strong reference (which is default) and thus when you're referencing self or inherently something is referencing self then you create a Retain Cycle.
Mind Map
How retain cycles occur in swift.
Retain Cycle
When to use weak capture list to break the retain cycle.
unowned vs weak
Advance
Optimization on Swift
Memory Graph
It is a great tool to quickly identify things across the application lifecycle. It create a graph like structure which shows strong or weak references of the objects currently in flight. You can also check if there are more objects being still in the memory after they have been dismissed or replaced by the navigation View controller by replacing or pushing one more view.
If the navigation uiview / uiviewcontroller stack is empty then by default swift / uikit / appkit or objective c runtime should automatically check the references and if the references are 0 zero then ARC (automatic reference counting) should do its job to deallocate those objects.
I still remember the C / C++ days of unsafe pointers, malloc
to allocate some memory and in order to save a string we needed to allocate each character byte to store a string. How far we have come in programming that an Open AI - Chat GPT 3 / 4 tool can easily generate code for us. Boilerplate code or just getting more information from the search engine but still cool nonetheless.
Objective C internally still has ways of Manual Memory management I believe. More control.
Memory Graph Debugger
memory-graph-debugging-in-xcode
Memory Management
How Heap and Stack memory work in Swift.
Article
Memory Leaks Instrument
medium | memory-management-in-swift-debugging-issues
Advance memory management
apple dev | Cocoa memory management
Transition from Obj - C to ARC
apple dev | Manual alloc - dealloc for references
apple dev | Managing lifetime of Objects from Nib Files
Storyboard
IBOutlets being referenced as weak references since the main parent object would be strong reference and it would deallocated all of its child IBOutlets when it goes off its screen.
iOS 6 also deprecated -viewDidUnload
which was used before in Obj - C to set the UIViews to nil
self.aSubView = nil;
SO | xcode-create-a-weak-reference-for-an-iboutlet
Closures
Inner Outer Closures
// Memory leak
.sink { [localDatabase] value in
private func observeOutput() {
remoteDataSource.sampleData
.sink { [unowned self] value in // now there's no retain cycle
self.localDatabase.save(result: value) { [weak self] in
self?.activityIndicator.stopAnimating()
self?.showAlert()
}
}
.store(in: &subscriptions)
}
It turns out that when we add [weak self]
in the inner closure, we need to provide a weak reference
to self
for the outer closure as well. If we don’t provide any, strong reference
will be used implicitly and we’ll get a retain cycle.
fix retain cycle in nested closures swift
capturing method reference
class ViewModel {
var cancellables = Set<AnyCancellable>()
init() {
publisher
.sink(receiveValue: handle(value:))
.store(in: &cancellables)
}
func handle(value: String) {
// `self` can be used here
}
}
Bad practice: capturing a method reference
This could be avoided two ways
Explicit capturing weak
publisher
.sink { [weak self] value in
self?.handle(value: value)
}
// `self` can be used here
func handle(value: String) { }
Closure computed property approach
publisher
.sink(receiveValue: handle(value:))
var handle(value: String) = { value
print(value)
}
Tasks
Swift Tasks implicitly capture strong self
class MyClass {
var myClosure: (() -> Void)?
deinit { print("deinit MyClass") }
}
class MyViewController: UIViewController {
var mc: MyClass!
override func viewDidLoad() {
super.viewDidLoad()
mc = MyClass()
mc.myClosure = {
Task { [weak self] in
// Do something with weak self
}
}
}
deinit { print("deinit MyViewController") }
}
adding [weak self]
to the inner closure causes the outer closure to capture self
strongly, which doesn't happen otherwise.
So the fix should be
- Either don't capture weak self in inner
Task
closure - Or capture weak self in outer closure.
mc.myClosure = { [weak self] in
// do something
}
// OR
mc.myClosure = {
Task {
// do something
}
}
Code snippet from swift forums
iOS 17 memory leaks with SwiftUI
This gets fixed if we not use fullscreenCover
and opt in for push
view controller instead.
De init
Object 0x10580ed00 deallocated with retain count 2, reference may have escaped from deinit.
You can hold a strong reference in the de-initializer of the object.
deinit {
  DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self] in
    self?.customP.destroy()
}
References
swift by sundell | swifts-closure-capturing-mechanics
Weak Self in Swift Made Easy: What it is and why it’s needed