So far, every property and method we've added to our structs has been freely accessible from anywhere. But that's not always what you want. Sometimes you need to protect certain data from being changed in ways that could break your logic. That's exactly what access control is for. ๐ฅ
The Problem Without Access Control
Let's say we're building a guild system for an anime RPG. A guild has a treasury, and members can deposit or withdraw gold โ but only through proper channels:
struct GuildTreasury {
var gold = 0
mutating func deposit(amount: Int) {
gold += amount
}
mutating func withdraw(amount: Int) -> Bool {
if gold >= amount {
gold -= amount
return true
} else {
print("Not enough gold!")
return false
}
}
}
This looks good โ we have proper deposit and withdraw methods that handle the logic. But here's the problem: gold is public by default, so nothing stops someone from doing this:
var fairyTail = GuildTreasury()
fairyTail.deposit(amount: 1000)
fairyTail.gold -= 9999 // ๐ฑ Bypasses all our logic!
That completely skips our validation logic. The guild treasury could go deeply negative, and our program would behave in unpredictable ways.
private โ Lock It Down
The fix is simple: mark gold as private. This tells Swift that gold can only be accessed inside the struct โ not from outside:
struct GuildTreasury {
private var gold = 0
mutating func deposit(amount: Int) {
gold += amount
}
mutating func withdraw(amount: Int) -> Bool {
if gold >= amount {
gold -= amount
return true
} else {
print("Not enough gold!")
return false
}
}
}
Now this would fail to compile:
fairyTail.gold -= 9999 // โ 'gold' is inaccessible due to 'private' protection level
But this still works perfectly:
fairyTail.deposit(amount: 1000) // โ
uses our controlled method
fairyTail.withdraw(amount: 200) // โ
uses our controlled method
Swift itself is now enforcing the rules โ you can't accidentally bypass the logic even if you tried. ๐
Important note: If you use
privatefor one or more properties, you'll likely need to write a custom initializer, since Swift's auto-generated memberwise initializer won't be able to set private properties from outside the struct.
private(set) โ Read Freely, Write Carefully
Sometimes you want the best of both worlds: anyone can read the value, but only the struct's own methods can change it.
That's exactly what private(set) does:
struct GuildTreasury {
private(set) var gold = 0
mutating func deposit(amount: Int) {
gold += amount
}
mutating func withdraw(amount: Int) -> Bool {
if gold >= amount {
gold -= amount
return true
} else {
print("Not enough gold!")
return false
}
}
}
var fairyTail = GuildTreasury()
fairyTail.deposit(amount: 1000)
print(fairyTail.gold) // โ
Reading is fine โ 1000
fairyTail.gold = 9999 // โ Writing directly is not allowed
This is perfect for our treasury โ members of the guild can check how much gold is in the pot, but they can't just set it to whatever they want. Only the official deposit and withdraw methods can change it.
The Access Control Options
Swift gives us a few levels to choose from:
| Keyword | What It Means |
|---|---|
private |
Only accessible inside this struct (or class/enum) |
fileprivate |
Accessible anywhere within the same Swift file |
public |
Accessible from anywhere โ inside or outside the module |
private(set) |
Anyone can read, but only the struct can write |
When you're learning and building apps, you'll mostly reach for private and private(set). The others become more relevant when you're building frameworks or larger multi-file projects.
Why Bother? You Could Just Not Break the Rules
You might be thinking: "I know not to bypass the withdrawal logic โ why do I need Swift to enforce it?"
Here's the honest answer: you are not always the person who will touch this code.
Maybe you write the GuildTreasury struct today, and six months from now you're in a rush to fix a bug and accidentally write directly to gold without thinking. Or maybe a teammate who joined the project later doesn't know about the rules. Or maybe you're using a library someone else wrote and you need to follow their rules.
Access control is a way of saying: "here's the interface you're supposed to use โ everything else is off limits." You're building a door and putting a lock on it, so that the only way in is through the proper entrance.
There's another benefit too: it reduces the surface area of your struct. If a struct has 15 properties and methods but 10 of them are private, anyone reading the code immediately knows: the 5 public ones are what I should be working with. The private ones are internal details, not something to worry about from the outside.
A Real-World Analogy
Think of a vending machine ๐ฅ. You can see how many snacks are left through the glass (read access). You can press a button and pay to get one (controlled write through a method). But you can't open the front panel and take snacks directly โ that access is locked.
private(set) is the glass panel. private is the locked door. The button is your deposit() or withdraw() method โ the only approved way in.
Wrap Up
| Concept | What It Means |
|---|---|
| Access control | Controlling who can read or write a struct's properties and methods |
private |
Only accessible inside the struct itself |
private(set) |
Anyone can read, only the struct can write |
public |
Accessible from anywhere |
| Why it matters | Prevents accidental misuse, enforces rules, reduces surface area |
Access control is one of those features that feels optional when you're just getting started, but becomes genuinely important the moment your code grows or gets shared with anyone else โ including future you. ๐ธ
This article was written by me; AI was used to improve grammar and readability.
Top comments (0)