Access control is a fundamental feature in Swift that allows you to define the visibility and accessibility of various parts of your code. By using access modifiers, you can encapsulate implementation details and restrict unintended access to your code, ensuring safety and modularity.
Swift offers five access levels:
- Open: The most permissive access level; it allows subclassing and overriding outside the module.
- Public: Allows access outside the module but prevents subclassing and overriding.
- Internal: Default access level, allowing access within the same module.
- Fileprivate: Restricts access to the same file.
- Private: The most restrictive level, limiting access to the enclosing declaration.
In this blog, we’ll dive into each of these access modifiers, explain their differences, and look at examples.
1. Open: Maximum Accessibility and Flexibility
open
is the most permissive access modifier in Swift. It allows the code entity (class, method, or property) to be accessed, subclassed, and overridden from any module, including outside the module in which it is defined. It is primarily used for frameworks where you intend to allow other developers to extend your code.
Example:
// Module A
open class Animal {
open func makeSound() {
print("Some generic sound")
}
}
// Module B
import ModuleA
class Cat: Animal {
override func makeSound() {
print("Meow!")
}
}
let myCat = Cat()
myCat.makeSound() // Output: "Meow!"
In this case, the Animal
class and its makeSound()
method are marked open, allowing them to be subclassed and overridden in another module.
Key Points:
- Accessible: Anywhere (inside and outside the module).
- Subclassing: Allowed in any module.
- Overriding: Allowed in any module.
2. Public: Controlled External Access
public
allows access to entities from outside the module, but unlike open
, it prevents subclassing and method overriding outside the module. This modifier is often used for APIs where you want to expose functionality but restrict further modification from external developers.
Example:
// Module A
public class Car {
public var model: String
public init(model: String) {
self.model = model
}
public func drive() {
print("\(model) is driving.")
}
}
// Module B
import ModuleA
let myCar = Car(model: "Tesla")
myCar.drive() // Output: "Tesla is driving"
// Cannot subclass or override 'Car' or 'drive' outside the module because 'public' does not allow it.
Key Points:
- Accessible: Anywhere (inside and outside the module).
- Subclassing: Not allowed outside the module.
- Overriding: Not allowed outside the module.
3. Internal: Default Module-Level Access
internal
is the default access level in Swift. It allows entities to be accessed from anywhere within the same module but restricts access from outside the module. This is suitable for internal implementation details of a framework or an application that don’t need to be exposed.
Example:
// Internal class
internal class Bike {
internal var brand: String
init(brand: String) {
self.brand = brand
}
internal func ride() {
print("\(brand) is being ridden.")
}
}
// This class can be accessed anywhere within the same module but not outside.
let myBike = Bike(brand: "Yamaha")
myBike.ride() // Output: "Yamaha is being ridden."
Key Points:
- Accessible: Anywhere within the same module.
- Subclassing and Overriding: Allowed within the module.
4. Fileprivate: Restricting Access to the Same File
fileprivate
restricts access to entities within the same source file. This access level is useful when you want multiple classes or extensions within the same file to access shared properties or methods but don’t want to expose those entities outside of the file.
Example:
// File: Vehicle.swift
class Truck {
fileprivate var weight: Int = 5000
fileprivate func loadCargo() {
print("Loading \(weight)kg of cargo.")
}
}
class Race {
func prepareTruck() {
let truck = Truck()
truck.loadCargo() // This works because 'loadCargo' is 'fileprivate' and accessed in the same file.
}
}
Trying to access the weight
property or loadCargo()
method outside the Vehicle.swift
file will result in an error.
Key Points:
- Accessible: Only within the same file.
- Subclassing and Overriding: Allowed within the same file.
5. Private: Limiting Access to the Enclosing Scope
private
is the most restrictive access modifier in Swift. It confines access to the enclosing declaration (such as a class, struct, or extension). This modifier ensures that properties or methods cannot be accessed or modified from outside the class or struct where they are defined, even within the same file (with an exception for extensions in the same file).
Example:
class Person {
private var age: Int
init(age: Int) {
self.age = age
}
private func displayAge() {
print("Age: \(age)")
}
}
let person = Person(age: 30)
// person.age // Error: 'age' is inaccessible due to 'private' protection level.
// person.displayAge() // Error: 'displayAge' is inaccessible due to 'private' protection level.
Key Points:
- Accessible: Only within the enclosing declaration (class, struct, or extension).
- Subclassing and Overriding: Not allowed outside the enclosing declaration.
Summary Table of Access Modifiers
Access Modifier | Accessibility | Subclassing | Overriding |
---|---|---|---|
open |
Anywhere (in and outside the module) | Allowed anywhere | Allowed anywhere |
public |
Anywhere (in and outside the module) | Allowed inside module only | Allowed inside module only |
internal |
Only within the same module | Allowed within module | Allowed within module |
fileprivate |
Only within the same file | Allowed within the file | Allowed within the file |
private |
Only within the same declaration | Not allowed | Not allowed |
Differences Between Open and Public
Feature | open | public |
---|---|---|
Access |
Accessible from anywhere (inside or outside the module) | Accessible from anywhere (inside or outside the module) |
Subclassing |
Allowed in any module | Allowed only within the same module |
Overriding |
Allowed in any module | Allowed only within the same module |
Real-World Use Cases
1. Framework Design:
When designing a Swift framework, you might use open
for classes or methods that you want others to be able to extend, while using public
for functionality that should be exposed but not modified.
// Public API for a Framework
open class Shape {
open func draw() {
print("Drawing a shape.")
}
}
public class Circle: Shape {
public override func draw() {
print("Drawing a circle.")
}
}
In this case, the Shape
class is open
, allowing external subclassing and overriding, while the Circle
class is public
, allowing external access but preventing subclassing outside the module.
2. Encapsulation:
Use private
to encapsulate sensitive data within a class. This ensures that critical properties and methods cannot be accessed or altered from outside.
class Account {
private var balance: Double = 0.0
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) {
if amount <= balance {
balance -= amount
}
}
}
3. File-Level Sharing:
Use fileprivate
to share methods or properties between classes within the same file but prevent access from outside.
fileprivate class Logger {
fileprivate func log(message: String) {
print("Log: \(message)")
}
}
class LoggerClient {
func useLogger() {
let logger = Logger()
logger.log(message: "This is a log message.")
}
}
Conclusion
Swift’s access control system allows developers to manage visibility and control access to various parts of the codebase. By using access modifiers like open
, public
, internal
, fileprivate
, and private
, you can build safer, more organized, and modular code. Each modifier serves a specific purpose, whether it’s ensuring external access, protecting sensitive data, or controlling subclassing behavior.
Understanding these access modifiers and knowing when to use them helps in maintaining clean code structure and preventing unintended usage or modification of your code.