Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
110: Access Control Levels In Depth
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,
openallows 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
.swiftfile 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
vaultBalanceshould be readable from outside the class, but only modifiable inside the class. - The
masterKeyshould be completely invisible to anything outside theSecureBankVaultclass. - The
depositandwithdrawmethods should remain accessible to the app. - The
printVaultDetailsmethod should be restricted so it's only usable within the same file (simulating a diagnostic tool used by the developer of this specific file).
There are no comments for now.