Skip to Content
Course content

110: Access Control Levels In Depth

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Think of your code like a high-security corporate headquarters. You wouldn't just give every visitor a master key to the server room, right? You have a lobby for the general public, a staff-only breakroom, and then you have your own private desk drawer that nobody else touches. Access control in Swift is exactly that—it's about deciding who gets a key to which room in your codebase.

If you make everything public, you're essentially leaving the front door wide open. It might seem easier at first because you never run into "cannot access" errors, but in a professional project, that's a recipe for disaster. You'll end up with other developers (or your future self) accidentally changing a variable that should have been read-only, breaking the entire system.

The Hierarchy of Keys

Let's map those office rooms to Swift's access levels. We'll use a PaymentProcessor example, because that's where security actually matters.

  • Open: This is like a franchise model. Not only can anyone use the store, but they can actually remodel the interior to fit their own needs. In Swift, open allows other modules to subclass your class or override your methods.
  • Public: This is the store window. People can see the products and buy them, but they can't move the walls or change how the store is run. It's visible outside your module, but cannot be subclassed or overridden.
  • Internal: This is the staff breakroom. Anyone who works for the company (anything inside the same module/target) can get in. This is the default in Swift—if you don't write anything, it's internal.
  • Fileprivate: This is a specific desk in a specific office. Only people who are physically in that same .swift file can touch it.
  • Private: This is your personal wallet. Only the specific class or extension it's defined in can see it.

The Subtle War Between Public and Open

I see a lot of developers use public when they actually mean open, or vice versa. Here is the rule of thumb: if you are building a library or a framework and you want the person using your library to be able to create a subclass of your class to add their own custom logic, you must use open.

// Inside a Framework
open class BasePaymentMethod {
    open func authorize() {
        print("Performing base authorization")
    }
}

// Inside the App using the framework
class ApplePayMethod: BasePaymentMethod { 
    override func authorize() {
        print("Using FaceID for authorization")
    }
}

If BasePaymentMethod was marked public, the code above would fail to compile. You can use the class, but you can't change its behavior via inheritance.

Hiding the Mess with Private and Fileprivate

You'll spend most of your time choosing between private and fileprivate. In the early days of Swift, fileprivate was used more often, but now private is much more powerful because it also allows access within extensions in the same file.

Take a look at this PaymentProcessor. I don't want the rest of the app to know how the API key is stored or how the raw request is formatted. That's "internal plumbing" that should be hidden.

class PaymentProcessor {
    // Only this class can see the API key. Total lockdown.
    private let apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    // Anyone in the app can trigger a payment
    internal func processPayment(amount: Double) {
        if validateAmount(amount) {
            sendRequest(amount)
        }
    }
    
    // This logic is helper-code; it doesn't need to be exposed
    private func validateAmount(_ amount: Double) -> Bool {
        return amount > 0
    }
    
    private func sendRequest(_ amount: Double) {
        print("Sending $\(amount) using key \(apiKey)")
    }
}

By marking apiKey and sendRequest as private, I've created a "black box." The rest of the team can call processPayment, but they can't accidentally mess with the API key or trigger a request without validation. This is the core of encapsulation.




📋 Practical Task

Refactoring the SecureBankVault for Encapsulation

You have been handed a SecureBankVault class that is way too permissive. Currently, the vault's balance and the secret master key are accessible from anywhere in the app, which is a massive security risk.

Your Goal: Refactor the following code to implement the strictest possible access control levels while still allowing the vault to be used by the app.

class SecureBankVault {
    var vaultBalance = 1000000.0
    var masterKey = "SECRET_12345"
    
    func deposit(amount: Double) {
        vaultBalance += amount
        print("Deposited \(amount). New balance: \(vaultBalance)")
    }
    
    func withdraw(amount: Double) {
        if amount <= vaultBalance {
            vaultBalance -= amount
            print("Withdrew \(amount).")
        } else {
            print("Insufficient funds!")
        }
    }
    
    func printVaultDetails() {
        print("Vault Balance: \(vaultBalance), Key: \(masterKey)")
    }
}

Requirements:

  • The vaultBalance should be readable from outside the class, but only modifiable inside the class.
  • The masterKey should be completely invisible to anything outside the SecureBankVault class.
  • The deposit and withdraw methods should remain accessible to the app.
  • The printVaultDetails method should be restricted so it's only usable within the same file (simulating a diagnostic tool used by the developer of this specific file).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.