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

Github markdown

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

raywenderlich | ARC in Swift

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()
}

swift forums link

References

swift docs | ARC

swift by sundell | swifts-closure-capturing-mechanics

Weak Self in Swift Made Easy: What it is and why it’s needed

Two ways of capturing self strongly within a closure

You don’t (always) need [weak self]