Memory Management
## Learning Objectives
- Understand ARC basics
- Work with weak and unowned references
- Avoid retain cycles
- Master memory management patterns
## Automatic Reference Counting
### How ARC Works
Swift uses Automatic Reference Counting (ARC) to manage memory:
```swift
class Person {
var name: String
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deallocated")
}
}
var person1: Person?
var person2: Person?
var person3: Person?
person1 = Person(name: "Alice") // Reference count: 1
person2 = person1 // Reference count: 2
person3 = person1 // Reference count: 3
person1 = nil // Reference count: 2
person2 = nil // Reference count: 1
person3 = nil // Reference count: 0 -> DEALLOCATED
```
### Reference Counting Rules
| Action | Effect |
|--------|--------|
| Assign to variable | +1 reference |
| Variable goes out of scope | -1 reference |
| Assign nil | -1 reference |
| Copy reference | +1 reference |
## Strong References
### Default Behavior
```swift
class Teacher {
var name: String
init(name: String) {
self.name = name
}
deinit {
print("\(name) deallocated")
}
}
let teacher = Teacher(name: "Mr. Smith")
// Strong reference by default
```
### Class Instance Lifecycle
```swift
class Customer {
let name: String
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deallocated")
}
}
func createCustomer() {
let customer = Customer(name: "Alice")
print("\(customer.name) created")
}
createCustomer()
// Output:
// Alice created
// Alice is being deallocated
```
## Weak References
### Declaration
```swift
weak var delegate: SomeDelegate?
```
### Use Case: Delegates
```swift
protocol ParentDelegate: AnyObject {
func childDidUpdate()
}
class Parent {
weak var delegate: ParentDelegate?
func update() {
delegate?.childDidUpdate()
}
}
class Child {
var parent: Parent?
init() {
parent = Parent()
parent?.delegate = self
}
deinit {
print("Child deallocated")
}
}
extension Child: ParentDelegate {
func childDidUpdate() {
print("Updated!")
}
}
var child: Child? = Child()
child = nil
// Child deallocated properly - no retain cycle
```
### Weak Reference Rules
- Always optional (`var`)
- Set to `nil` when referenced object deallocates
- Use for delegates and parent references
## Unowned References
### Unowned Declaration
```swift
unowned var reference: SomeType
```
### Use Case: Captured References
```swift
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit {
print("Customer \(name) deallocated")
}
}
class CreditCard {
let number: Int
unowned let owner: Customer
init(number: Int, owner: Customer) {
self.number = number
self.owner = owner
}
deinit {
print("Card \(number) deallocated")
}
}
var customer: Customer? = Customer(name: "Alice")
customer?.card = CreditCard(number: 1234, owner: customer!)
// CreditCard's unowned owner doesn't increase retain count
customer = nil
// Customer deallocated, then CreditCard deallocated
```
### unowned vs weak
| Feature | weak | unowned |
|---------|------|---------|
| Type | Optional | Non-optional |
| Value when deallocated | nil | Crashes if accessed |
| Use when | Can be nil | Never nil |
| Typical use | Delegates | Parent/child |
## Retain Cycles
### What is a Retain Cycle?
```swift
class Person {
var name: String
var friend: Person? // Strong by default
init(name: String) {
self.name = name
}
deinit {
print("Person \(name) deallocated")
}
}
var alice: Person?
var bob: Person?
alice = Person(name: "Alice")
bob = Person(name: "Bob")
alice?.friend = bob // Alice -> Bob (strong)
bob?.friend = alice // Bob -> Alice (strong)
// Retain cycle: Alice <-> Bob
// Neither will ever be deallocated!
```
### Breaking the Cycle
```swift
class Person {
var name: String
weak var friend: Person? // weak breaks the cycle
init(name: String) {
self.name = name
}
deinit {
print("Person \(name) deallocated")
}
}
```
## Closure Captures
### Closures and References
```swift
class ClosureHandler {
var value = 10
lazy var closure: () -> Int = {
return self.value // Strong capture of self
}
}
```
### Capture Lists
```swift
class Handler {
var value = 10
lazy var weakClosure: () -> Int = { [weak self] in
return self?.value ?? 0 // Weak capture
}
lazy var unownedClosure: () -> Int = { [unowned self] in
return self.value // Unowned capture
}
}
```
### Why Capture Lists?
```swift
class MyViewController {
var data: [String] = []
func loadData() {
// Without capture list - RETAIN CYCLE!
// completion: () -> Void = { self.data = [...] }
// With capture list - safe
API.fetch { [weak self] result in
self?.data = result
}
}
}
```
## Memory Management Patterns
### Delegate Pattern
```swift
protocol ManagerDelegate: AnyObject {
func didComplete()
}
class Manager {
weak var delegate: ManagerDelegate?
}
```
### Parent-Child References
```swift
class Parent {
var children: [Child] = []
func addChild(_ child: Child) {
children.append(child)
child.parent = self // Strong
}
}
class Child {
weak var parent: Parent? // Weak - prevents cycle
}
```
### Data Source Pattern
```swift
class TableViewCell {
weak var dataSource: TableViewDataSource?
}
```
## Debugging Memory Issues
### Memory Graph Debugger (Xcode)
1. Run app in debug mode
2. Debug > Debug Memory Graph
3. Look for cycles and leaks
### Instruments
- Allocations instrument
- Leaks instrument
- Core Allocation instrument
### Common Symptoms
- Memory keeps growing
- deinit never called
- App using more RAM than expected
## Best Practices
### Do
```swift
// Use weak for delegates
weak var delegate: MyDelegate?
// Use weak/weak self in closures
someAsyncCall { [weak self] result in
self?.handle(result)
}
// Use unowned when safe
lazy var closure: () -> String = { [unowned self] in
return self.id
}
```
### Don't
```swift
// Don't create strong reference cycles
class A {
var b: B?
}
class B {
var a: A? // Cycle if both strongly reference each other
}
// Don't use unowned when optional
// BAD: unowned var optionalRef: String? (compiler error)
```
## Value Types vs Reference Types
### Value Types (Struct, Enum)
```swift
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 1, y: 2)
var p2 = p1 // Copy! No reference counting
p2.x = 10
print(p1.x) // 1 (unchanged)
print(p2.x) // 10
```
### Reference Types (Class)
```swift
class PointClass {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
var p1 = PointClass(x: 1, y: 2)
var p2 = p1 // Same reference!
p2.x = 10
print(p1.x) // 10 (changed!)
print(p2.x) // 10
```
## Summary
- ARC manages memory automatically for class instances
- Strong references increase retain count
- Weak references (`weak var`) don't increase count, can be nil
- Unowned references (`unowned var`) don't increase count, never nil
- Retain cycles occur with strong circular references
- Use weak for delegates and parent references
- Use capture lists in closures: `[weak self]` or `[unowned self]`
- Value types (structs) are copied, not reference-counted
- Reference types (classes) share references
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →