Understanding Access Modifiers in Swift: A Clear, Practical Guide
When building apps in Swift, you quickly discover that organizing and protecting your code is just as important as making it work. That’s where access control comes in. Swift’s access modifiers help you manage which parts of your code can be seen or used outside of their defining context—whether that’s a file, a module, or a specific scope.
Swift provides five levels of access: open, public, internal, fileprivate, and private. In this post, we’ll break down each one, show when to use them, and give practical examples to make everything click.
Why Access Control Matters
Access control helps you:
- Encapsulate implementation details
- Prevent accidental misuse of APIs
- Keep your codebase clean and maintainable
- Design clear, intentional interfaces
Think of it as giving your code a set of permissions—like deciding which rooms in your digital “house” guests are allowed to enter.
1. open — The Most Permissive (For Frameworks)
open is the highest access level in Swift. It allows code to be accessed outside the module and also allows subclassing and overriding from other modules.
Use it when you’re building frameworks or libraries meant to be extended.
Example:
open class Animal {
open func speak() {
print("Some sound")
}
}
2. public — Accessible Outside the Module, But Not Override-Friendly
public also allows entities to be used outside your module, but you cannot subclass or override them from outside the module.
It’s ideal for frameworks when you want to expose functionality but not allow external customization.
Example:
public class MathUtils {
public static func square(_ x: Int) -> Int {
return x * x
}
}
3. internal — The Default Access Level
internal is Swift’s default. It allows code to be accessed anywhere within the same module, but not by external modules.
Use it for most app code. If you don’t specify a modifier, you’re using internal.
Example:
internal struct User {
var name: String
}
4. fileprivate — Visible Only in the Same File
fileprivate restricts access to the current source file. This is useful when you need helper functions or extensions that should stay hidden from the rest of the module.
Example:
fileprivate func generateSecretKey() -> String {
return "abc123"
}
5. private — The Most Restrictive (Within a Declaration)
private makes declarations accessible only within the same scope (e.g., a class or struct). It’s your go-to choice for encapsulating implementation details.
Example:
class BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
}
Quick Comparison Table
| Modifier | Accessible Outside Module? | Subclassable Outside Module? | Typical Use |
|---|---|---|---|
| open | Yes | Yes | Extensible frameworks |
| public | Yes | No | Exposed APIs |
| internal | No | — | Default app code |
| fileprivate | No (file only) | — | File-level helpers |
| private | No (scope only) | — | Encapsulation |
Choosing the Right Modifier
Here are some quick guidelines:
- Use
privatefor implementation details you don’t want exposed. - Use
fileprivatewhen multiple declarations in a file need to cooperate. - Use
internal(default) for all normal application logic. - Use
publicto expose APIs without allowing external subclassing. - Use
openonly when you explicitly intend extensibility across modules.