Automatic Reference Counting (ARC)
Swift manages memory automatically using ARC. Every time you create an object, Swift keeps a count of how many things are using it. When that count hits zero, the memory gets freed up. You don't call malloc or free โ Swift handles it. ARC is fast, but it needs you to understand references to avoid leaks.
class Person {
let name: String
init(name: String) { self.name = name }
deinit { print("\(name) is being deallocated") }
}
var person: Person? = Person(name: "Alice")
person = nil // "Alice is being deallocated"
Try it Yourself ->
Strong References
By default, every reference is strong. A strong reference keeps the object alive. As long as at least one strong reference exists, ARC won't free the memory. This is usually what you want, but it can cause problems when objects reference each other.
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) { self.unit = unit }
deinit { print("Apartment \(unit) is being deallocated") }
}
var apartment: Apartment? = Apartment(unit: "4B")
var person: Person? = Person(name: "Bob")
apartment?.tenant = person // strong reference from apartment to person
person = nil // person still alive because apartment holds it
apartment = nil // now both are deallocated
Try it Yourself ->
Retain Cycles
A retain cycle happens when two objects hold strong references to each other. Neither gets deallocated because ARC thinks the other still needs it. This is a memory leak โ your app keeps using more and more memory for nothing.
class Person {
let name: String
var apartment: Apartment?
init(name: String) { self.name = name }
deinit { print("\(name) deallocated") }
}
class Apartment {
let unit: String
var tenant: Person?
init(unit: String) { self.unit = unit }
deinit { print("Apartment \(unit) deallocated") }
}
var alice: Person? = Person(name: "Alice")
var apt4A: Apartment? = Apartment(unit: "4A")
alice?.apartment = apt4A
apt4A?.tenant = alice
alice = nil
apt4A = nil
// Neither is deallocated โ retain cycle!
Try it Yourself ->
Weak References
Use weak to break retain cycles. A weak reference doesn't increase the reference count, and it automatically becomes nil when the object is freed. This is perfect for child references back to a parent.
class Person {
let name: String
var apartment: Apartment?
init(name: String) { self.name = name }
deinit { print("\(name) deallocated") }
}
class Apartment {
let unit: String
weak var tenant: Person? // weak breaks the cycle
init(unit: String) { self.unit = unit }
deinit { print("Apartment \(unit) deallocated") }
}
var alice: Person? = Person(name: "Alice")
var apt4A: Apartment? = Apartment(unit: "4A")
alice?.apartment = apt4A
apt4A?.tenant = alice
alice = nil // "Alice deallocated"
apt4A = nil // "Apartment 4A deallocated"
Try it Yourself ->
Unowned References
unowned is like weak but non-optional โ it won't become nil. Use it when you're absolutely sure the referenced object will outlive the reference. If the object gets freed while an unowned reference exists, your app crashes.
class Customer {
let name: String
var card: CreditCard?
init(name: String) { self.name = name }
deinit { print("\(name) deallocated") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer // customer always outlives the card
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card \(number) deallocated") }
}
var customer: Customer? = Customer(name: "John")
customer?.card = CreditCard(number: 1234_5678_9012_3456, customer: customer!)
customer = nil // both deallocated
Try it Yourself ->
Closure Capture Lists
Closures can also cause retain cycles. When a closure captures self strongly and self holds the closure, you get a cycle. Use a capture list [weak self] or [unowned self] to break it.
class NetworkManager {
var onComplete: (() -> Void)?
func fetchData() {
onComplete = { [weak self] in
guard let self = self else { return }
print("Data fetched for \(self)")
}
}
deinit { print("NetworkManager deallocated") }
}
var manager: NetworkManager? = NetworkManager()
manager?.fetchData()
manager = nil // deallocated because weak self breaks the cycle
Try it Yourself ->