Access Control in Swift: `public`, `private`, `internal`, and More
At Pixar’s studio, not everyone has the same access. Visitors can tour the lobby and gift shop. Animators get into the workstations and render labs. Directors can enter any room — including the story vault where unreleased plots are locked away. Swift’s access control works the same way: it controls which parts of your code can see and use which other parts.
In this guide, you’ll learn Swift’s five access levels, when to use each, and how they help you write cleaner, safer APIs. We won’t cover module architecture or Swift Package Manager — those have their own dedicated posts.
What You’ll Learn
- What Is Access Control?
- The Five Access Levels
privateandfileprivatepublicvsopen- Access Control in Practice
- Common Mistakes
- What’s Next?
What Is Access Control?
Access control restricts which parts of your code can access specific types, properties, and methods. It’s how you enforce encapsulation — hiding internal details so that other code can only interact with your type through its intended interface.
Think of it like backstage passes at a Pixar movie premiere. The audience (external code) can only see what’s on screen — the finished movie. The production crew (same module) can access the editing room, the sound stage, and the animation files. And the director (same declaration) has access to everything, including early story drafts that nobody else should touch.
Apple Docs:
Access Control— The Swift Programming Language
The Five Access Levels
Swift provides five access levels, from most restrictive to most open:
| Access Level | Scope | When to Use |
|---|---|---|
private | Same declaration (type or extension in same file) | Implementation details that nothing else should touch |
fileprivate | Same source file | When multiple types in the same file need to share details |
internal | Same module (default) | Most of your app code — visible within your project |
public | Any module that imports yours | Library APIs — usable but not subclassable outside |
open | Any module that imports yours | Library APIs — usable and subclassable/overridable outside |
The default access level is internal — if you don’t write an access keyword, your code is visible throughout your
module (your app or framework) but not outside it.
struct Movie {
var title: String // internal (default)
var year: Int // internal (default)
}
Both properties are internal — accessible anywhere in the same module.
Tip: You almost never need to write
internalexplicitly. It’s the default, and writing it adds noise without changing behavior.
private and fileprivate
private
private is the most restrictive level. A private member is only visible within the declaration where it’s defined —
and any extensions of that type in the same file:
struct PixarCharacter {
private var secretCatchphrase = "I'm a hidden gem"
var name: String
init(name: String) {
self.name = name
}
func reveal() {
print("\(name) whispers: \(secretCatchphrase)")
}
}
let woody = PixarCharacter(name: "Woody")
woody.reveal()
// woody.secretCatchphrase // ❌ Error: 'secretCatchphrase' is private
Woody whispers: I'm a hidden gem
fileprivate
fileprivate opens access to everything in the same source file. This is useful when you have helper types or
extensions in the same file that need to share data:
struct Animation {
fileprivate var frameCount: Int = 0
mutating func addFrame() {
frameCount += 1
}
}
extension Animation {
func summary() -> String {
// ✅ Works because fileprivate allows same-file access
"Animation has \(frameCount) frames"
}
}
var anim = Animation()
anim.addFrame()
anim.addFrame()
print(anim.summary())
Animation has 2 frames
If frameCount were private instead of fileprivate, the extension would still be able to access it only if it’s in
the same file (Swift allows private access from extensions in the same file). But fileprivate also lets other
types in the same file access it:
struct AnimationDebugger {
func inspect(_ animation: Animation) {
// ✅ Works with fileprivate — same file
print("Frames: \(animation.frameCount)")
}
}
Frames: 2
Note: In Swift,
privatemembers are accessible from extensions of the same type in the same file. This was changed in Swift 4 to make extensions more practical.
public vs open
When building a library or framework, you need public or open to expose APIs to other modules.
public lets external code use your type but not subclass it or override its methods:
// In a framework called PixarKit
public struct RenderSettings {
public var quality: Int
public init(quality: Int) {
self.quality = quality
}
}
open goes further — external code can subclass the class and override its methods:
// In a framework called PixarKit
open class Renderer {
open func render() {
print("Default rendering")
}
}
// In the app that imports PixarKit
// class CustomRenderer: Renderer {
// override func render() {
// print("Custom rendering with sparkles!")
// }
// }
Use public when you want external code to use your API.
Use open when you want external code to customize it via subclassing.
Tip: In app code (not a framework), you rarely need
publicoropen. These matter when you’re building reusable libraries or Swift packages that other projects import.
Access Control in Practice
Here’s a practical example showing how access control protects a character’s internal state:
struct GameCharacter {
let name: String
private(set) var health: Int
init(name: String, health: Int) {
self.name = name
self.health = health
}
mutating func takeDamage(_ amount: Int) {
health = max(0, health - amount)
print("\(name) took \(amount) damage! Health: \(health)")
}
mutating func heal(_ amount: Int) {
health = min(100, health + amount)
print("\(name) healed! Health: \(health)")
}
}
var buzz = GameCharacter(name: "Buzz", health: 100)
buzz.takeDamage(30)
buzz.heal(10)
print(buzz.health) // ✅ Can read health
// buzz.health = 999 // ❌ Can't set — private(set)
Buzz took 30 damage! Health: 70
Buzz healed! Health: 80
80
The private(set) modifier is a common pattern: it lets external code read the property but only the type itself can
write to it. The character’s health can only change through controlled methods like takeDamage() and heal() —
nobody can cheat by setting health = 999 directly.
Tip:
private(set)is one of the most useful access control patterns in Swift. It gives you read-only properties from the outside while keeping full control inside.
Common Mistakes
Making Everything private
Over-restricting can backfire when extensions in the same file can’t access what they need:
// ❌ Too restrictive — other types in the file can't access
struct StudioAsset {
private var data: [String] = []
}
// A helper in the same file can't see 'data'
// struct AssetLoader {
// func load(into asset: StudioAsset) {
// asset.data.append("frame.png") // ❌ Error
// }
// }
// ✅ Use fileprivate when same-file access is needed
struct StudioAsset {
fileprivate var data: [String] = []
}
struct AssetLoader {
func load(into asset: inout StudioAsset) {
asset.data.append("frame.png") // ✅ Works
print("Loaded \(asset.data.count) asset(s)")
}
}
Loaded 1 asset(s)
Forgetting That internal Is the Default
// ❌ Unnecessary — internal is already the default
internal struct Movie {
internal var title: String
internal var year: Int
}
// ✅ Clean — no need to write internal explicitly
struct Movie {
var title: String
var year: Int
}
Writing internal explicitly adds visual noise without changing behavior. Save access control keywords for when you’re
intentionally restricting (private, fileprivate) or expanding (public, open) access beyond the default.
What’s Next?
- Access control restricts visibility of your code to enforce encapsulation.
- Swift has five levels:
private,fileprivate,internal(default),public, andopen. - Use
privatefor implementation details. Usefileprivatefor same-file sharing. publicexposes APIs to other modules;openadditionally allows subclassing.private(set)is a practical pattern for read-only properties with internal write access.
With protocols defining contracts and access control protecting your APIs, you have a solid foundation for writing well-structured Swift code. Revisit Protocols in Swift and Extensions in Swift to see how these concepts work together in practice.