What access control does
An access modifier decides which code may use a member of a type. If every property is open to everyone, any code can change any value, and if you later change how a value is stored or validated, every caller that touched it directly may break. Hiding data and exposing a small set of methods is called encapsulation.
The Swift levels
Swift has five access levels, from most to least open:
| Level | Visible |
|---|---|
open | Everywhere, and classes can be subclassed or overridden from other modules. |
public | Everywhere, but no subclassing or overriding outside the defining module. |
internal | Anywhere in the same module. This is the default when you write nothing. |
fileprivate | Anywhere in the same source file. |
private | Only inside the enclosing declaration (and extensions of it in the same file). |
For these lessons, the two that matter are the default (members are reachable from other code) and private. Swift has no protected keyword and no package-private level like some other languages.
Making a property private
class Dog {
private let name: String
init(_ name: String) {
self.name = name
}
func getName() -> String {
return name
}
}
Inside Dog, name is used directly. Outside the class, it is off limits. Even though Solution is in the same file, writing rex.name there fails with 'name' is inaccessible due to 'private' protection level, because private is scoped to the class, not the file. The public way in is the method: rex.getName() returns the value without allowing a change.
The lesson's property is private let, so it can be set once by the initializer and never changed afterwards, not even inside the class. A private var could still be changed by the class's own methods.
A read-only property without a getter
Swift offers a shortcut for the common case of "readable by everyone, writable only by the class": private(set).
class Account {
private(set) var balance: Double = 0
func deposit(_ amount: Double) {
if amount < 0 { return }
balance += amount
}
}
let account = Account()
account.deposit(5)
print(account.balance) // 5.0
Reading account.balance is allowed from outside, and assigning to it is not: account.balance = 100 fails with cannot assign to property: 'balance' setter is inaccessible. Only deposit can change the balance, so it can enforce the rule that deposits are not negative. Swift code often uses this form in place of a getBalance() method.
The exercise
The starter Dog has a private let name, and mainFunction prints "Dog's name: " + rex.getName(). Add the missing method, first with an empty body and a String return type, then returning the name:
class Dog {
private let name: String
init(_ name: String) {
self.name = name
}
func getName() -> String {
return name
}
}
class Solution {
func mainFunction() {
let rex = Dog("Rex")
print("Dog's name: " + rex.getName())
}
}
The output is Dog's name: Rex.
Why bother hiding anything
Suppose Account let anyone write to balance. Any line of code in the program could set it to a negative number, and a bug could appear far from the line that caused it. With the property restricted, the places that can change it are exactly the methods inside the class, so you only have to look there. The class can also change its internals later, for example storing cents as an Int instead of a Double, without touching a single caller, as long as the public methods keep their signatures.
Common mistakes
- Reading the private property directly from outside the class. Use the method.
- Returning nothing. A method declared
-> Stringneeds areturn. - Making the getter private as well. Then no outside code can read the name at all.
- Making everything private. Hide what callers must not touch, and expose what they need.
Key takeaways
- Members are internal by default;
privatelimits them to their own declaration. - Expose data through methods, or through
private(set)for a read-only property. - Swift's levels are
open,public,internal,fileprivateandprivate.
