Swift Access Control

Access control decides which parts of your code other files and other modules may see. Good programs expose a small, clear surface and hide the messy details. Hidden details change freely without breaking the code that depends on the visible parts.

Picture an office building. Visitors enter the lobby, employees use the working floors, and only the manager opens the vault. Swift offers five access levels that follow the same idea.

Diagram: Rings of Access

 +---------------------------------------------+
 | open / public   : anyone, any module        |
 |  +---------------------------------------+  |
 |  | internal    : same module (default)   |  |
 |  |  +---------------------------------+  |  |
 |  |  | fileprivate : same file only    |  |  |
 |  |  |  +---------------------------+  |  |  |
 |  |  |  | private : same scope only |  |  |  |
 |  |  |  +---------------------------+  |  |  |
 |  |  +---------------------------------+  |  |
 |  +---------------------------------------+  |
 +---------------------------------------------+

The Five Access Levels

  • open allows use and subclassing from any module. Classes and class members only.
  • public allows use from any module without outside subclassing.
  • internal allows use inside the same module. Swift applies this level when you write nothing.
  • fileprivate allows use inside the same source file.
  • private allows use inside the same declaration and its extensions in the same file.

A module is one unit of code, such as an app or a framework. Most beginner apps use a single module. The internal level covers nearly everything.

Private Members

Mark internal workings as private. Outside code cannot touch them.

class BankAccount {
    private var transactionLog: [String] = []
    private(set) var balance: Double = 0

    func deposit(_ amount: Double) {
        guard amount > 0 else { return }
        balance += amount
        log("Deposit \(amount)")
    }

    private func log(_ entry: String) {
        transactionLog.append(entry)
    }
}

let account = BankAccount()
account.deposit(100)
print(account.balance)   // Output: 100.0

// account.balance = 5000     // Error: setter is private
// account.log("Hack")        // Error: method is private

The modifier private(set) lets everyone read balance and allows only the class itself to change it. This pattern protects important values from careless edits.

Fileprivate Members

Use fileprivate when several types in one file must cooperate while the rest of the app stays out.

fileprivate func formatPrice(_ value: Double) -> String {
    "$" + String(format: "%.2f", value)
}

struct Invoice {
    let amount: Double
    var label: String { formatPrice(amount) }
}

print(Invoice(amount: 49.5).label)   // Output: $49.50

The call to String(format:) needs import Foundation in a standalone file.

Public and Open for Frameworks

Developers who build frameworks and packages mark their outward-facing types as public. Everything else stays internal.

public struct Greeter {
    public init() {}

    public func hello(_ name: String) -> String {
        "Hello, \(name)"
    }
}

A public struct needs a public initializer because the automatic initializer stays internal. The difference between open and public shows up only with classes: outside modules can subclass and override open members and cannot override public members.

Access Rules to Remember

  • A public function cannot use a private type as a parameter or return value.
  • A function never has a higher access level than the types it uses.
  • Members of a private type default to private or fileprivate behavior.

Choosing a Level

Start with the most restrictive level and open a member only when another part of the code needs it. Restrictive defaults keep the design tidy and keep bugs close to their source.

Common Mistakes

Beginners often mark every member as public out of habit. Open doors invite misuse, and every public member becomes a promise you must keep for years. Hide members until a real need appears.

Another mistake involves forgetting the access level of an initializer. A public struct still hides its automatic initializer, and other modules fail to create the struct. Write a public initializer by hand.

Real-World Use

Banking apps, game scoreboards, and shopping carts all guard their balances and totals with private(set). The screen reads the value and only the model changes it. Bugs shrink because one small section of code owns every change.

Key Takeaways

  • Swift offers open, public, internal, fileprivate, and private.
  • The default level is internal.
  • private(set) allows public reading with restricted writing.
  • Hide details by default and share only what other code needs.