Chapter 84: Swift self Keyword
1. What does self really mean? (the clearest possible explanation)
self is the current instance — the specific object that is currently executing the code.
Think of it as the object answering the phone when one of its own methods is called.
Real-life analogy:
Imagine a classroom full of students. Each student is an instance of the “Person” class.
When the teacher says: “Everyone, please say your own name out loud”
Each student says their own name — not someone else’s.
In code, when a method runs, self is the student who is currently speaking.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Student { let name: String init(name: String) { self.name = name // "my own name is Rahul" } func introduce() { print("Hi, I'm \(self.name)") // "my own name" } } let rahul = Student(name: "Rahul") let priya = Student(name: "Priya") rahul.introduce() // Hi, I'm Rahul ← self = rahul priya.introduce() // Hi, I'm Priya ← self = priya |
So:
- self = whoever is currently executing this method
- Every time a method runs, Swift automatically sets self to the current instance
2. The three main situations where you see self
Swift has very clear rules — once you understand them, self stops being confusing.
Situation 1 — You must write self when there is name conflict
This is the most common reason you see self..
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Person { var name: String init(name: String) { // Name conflict! Parameter name and property name are the same self.name = name // self.name ← property, name ← parameter } func updateName(newName: String) { // Again — name conflict self.name = newName } } |
Rule #1:
You must write self. when the property name and a parameter / local variable have the same name.
This is by far the most frequent place you will type self.
Situation 2 — You must write self inside closures that capture self
Closures capture variables — and when they capture self, you usually need to write it explicitly (especially with [weak self]).
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class UserProfileViewModel { var username: String = "guest" func fetchProfile() { // Network call simulation DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in guard let self else { return } self.username = "priya_reddy" print("Updated username to: \(self.username)") } } } |
Rule #2:
Inside closures, you almost always write self. (and usually use [weak self] or [unowned self] to avoid retain cycles)
Situation 3 — You can write self for clarity — even when not required
In many places Swift lets you omit self., but many teams still write it for readability.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Counter { var count = 0 func increment() { count += 1 // works without self self.count += 1 // also works — more explicit } func reset() { self.count = 0 // many teams prefer writing self here } } |
Rule #3:
You may write self. even when Swift doesn’t require it — many style guides recommend it for clarity, especially:
- in longer methods
- when mixing local variables and properties
- in team projects
4. Real-life examples — where you will actually write self
Example 1 – Constructor / initializer (most common place for self)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Product { let id: String let name: String var price: Double var quantity: Int init(id: String, name: String, price: Double, quantity: Int = 1) { self.id = id // must use self because parameter has same name self.name = name self.price = price self.quantity = quantity } } |
→ Almost every initializer with parameters matching property names uses self.
Example 2 – Method with parameter name conflict
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ShoppingCart { private var items: [String] = [] func addItem(item: String) { self.items.append(item) // self.items — name conflict with parameter print("Added \(item). Total items: \(self.items.count)") } func clear() { items.removeAll() // no conflict → self. is optional // self.items.removeAll() // also fine — more explicit } } |
Example 3 – Closure capturing self (very frequent in networking, UI, timers)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class TimerViewModel { var secondsLeft = 60 var timer: Timer? func startCountdown() { timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in guard let self else { return } self.secondsLeft -= 1 print("Time left: \(self.secondsLeft)") if self.secondsLeft <= 0 { self.timer?.invalidate() self.onCountdownFinished() } } } func onCountdownFinished() { print("Countdown finished!") } } |
Very important modern habit:
Always use [weak self] (or [unowned self]) in closures that capture self → prevents memory leaks (retain cycles)
5. When Swift lets you omit self. (and when teams still write it)
You can omit self. in these cases:
- No name conflict with parameters or local variables
- Not inside a closure
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Counter { var count = 0 func increment() { count += 1 // OK — no conflict print("Count is now \(count)") } func reset(to newValue: Int) { count = newValue // OK — newValue is different name } } |
But many teams still write self. everywhere:
|
0 1 2 3 4 5 6 7 8 9 |
func increment() { self.count += 1 print("Count is now \(self.count)") } |
Why?
- Makes it immediately clear that you are accessing a property (not a local variable)
- Easier to search/refactor
- Consistent style in large codebases
6. Very Common Beginner Mistakes & Correct Habits
| Mistake | Wrong / Risky code | Correct / Better habit | Why? |
|---|---|---|---|
| Forgetting self in initializer | init(name: String) { name = name } | self.name = name | Name conflict — compile error without self |
| Force-unwrapping self in closures | { self.doSomething() } without capture list | { [weak self] in self?.doSomething() } | Retain cycle / memory leak |
| Using var self in closures | [self] in self.count += 1 | [weak self] or [unowned self] | Strong reference cycle |
| Writing self when not needed (but inconsistently) | sometimes self.count, sometimes count | Decide team style: either always or never | Consistency improves readability |
| Thinking self is optional everywhere | self omitted in closures | Always explicit in closures | Capture list requires clarity |
7. Quick Summary — When you must write self
| Situation | Must write self? | Example | Reason |
|---|---|---|---|
| Name conflict (parameter or local var) | Yes | init(name: String) { self.name = name } | Compiler requires it |
| Inside a closure that captures self | Yes (usually) | { [weak self] in self?.count += 1 } | Capture list + safety |
| Accessing property in computed property/setter | Usually yes | var fullName: String { self.first + ” ” + self.last } | Clarity — many teams always write it |
| No name conflict, not in closure | Optional | count += 1 or self.count += 1 | Style choice — many teams prefer self. |
8. Small Practice — Try these
- Create a Person class with:
- let name: String
- var age: Int
- initializer that sets both
- method introduce() that uses self.name and self.age
- Create a Counter class with:
- var count: Int = 0
- method increment(by step: Int = 1) that uses self.count
- Create a class that uses a closure (Timer or DispatchQueue) and correctly captures [weak self]
Paste your code here if you want feedback or want to see more polished versions!
What would you like to explore next?
- self in closures — [weak self], [unowned self], capture lists
- self vs Self (static vs instance context)
- mutating self in structs vs classes
- self in SwiftUI (ObservableObject, @StateObject…)
- Or move to another topic (optionals, arrays, closures, switch…)
Just tell me — we’ll continue in the same clear, detailed, patient style 😊
