Swift Private Access
Introduction
In Swift, access control helps you specify which parts of your code can be accessed from other parts of your code. The private
access modifier is one of the most commonly used access levels in Swift, providing a strong form of encapsulation by hiding implementation details.
When you mark a property, method, or type as private
, you restrict its usage to only within the scope where it's defined. This helps in hiding implementation details and maintaining a clean, well-defined interface for your types.
Understanding Private Access
Basic Definition
The private
access restricts the use of an entity to its defining source file. When you mark something as private
, you're saying: "This can only be used within the same file where it's defined."
class BankAccount {
private var accountNumber: String
private var balance: Double
init(accountNumber: String, initialBalance: Double) {
self.accountNumber = accountNumber
self.balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func getBalance() -> Double {
return balance
}
}
// Usage
let account = BankAccount(accountNumber: "12345", initialBalance: 1000)
account.deposit(amount: 500)
print(account.getBalance()) // Output: 1500
// This would cause a compile-time error:
// print(account.balance) // Error: 'balance' is inaccessible due to 'private' protection level
In this example:
accountNumber
andbalance
are private, meaning they can only be accessed within theBankAccount
class- We provide public methods
deposit()
andgetBalance()
to interact with these private properties - Attempting to access
balance
directly from outside the class would result in a compilation error
Private Access Scope
It's important to understand the exact scope of private
access. Prior to Swift 4, private
access was limited to the declaring class itself. Extensions of that class in the same file couldn't access those private members.
Swift 4 introduced file-private access (which we'll discuss in a related tutorial), and redefined private
to be accessible within extensions of the same type in the same file:
class Person {
private var socialSecurityNumber: String
init(ssn: String) {
self.socialSecurityNumber = ssn
}
}
// Extension in the same file
extension Person {
func isValidSSN() -> Bool {
// We can access socialSecurityNumber here because this extension
// is in the same file as the Person class
return socialSecurityNumber.count == 9
}
}
However, private
members are not accessible from extensions in different files:
// In a different file
extension Person {
func printSSNLength() {
// This would cause an error:
// print(socialSecurityNumber.count)
}
}
Common Use Cases for Private Access
1. Encapsulating Implementation Details
class ShoppingCart {
private var items: [String] = []
func add(item: String) {
items.append(item)
}
func remove(item: String) {
if let index = items.firstIndex(of: item) {
items.remove(at: index)
}
}
func getItemCount() -> Int {
return items.count
}
func listItems() -> [String] {
return items
}
}
In this example, the array of items
is private, but we provide public methods to interact with it. This prevents external code from directly manipulating the array, which might lead to inconsistencies.
2. Hiding Helper Methods
class PaymentProcessor {
func processPayment(amount: Double, creditCard: String) -> Bool {
if !validateCreditCard(creditCard) {
return false
}
// Process the payment
print("Processing payment of $\(amount)")
return true
}
private func validateCreditCard(_ cardNumber: String) -> Bool {
// Complex validation logic
return cardNumber.count >= 16
}
}
let processor = PaymentProcessor()
processor.processPayment(amount: 99.99, creditCard: "1234567890123456")
// Output: Processing payment of $99.99
// This would cause a compile-time error:
// processor.validateCreditCard("1234") // Error: 'validateCreditCard' is inaccessible due to 'private' protection level
Here, validateCreditCard
is an internal helper method that shouldn't be called directly by users of the PaymentProcessor
class.
3. Private Properties with Custom Getters/Setters
class TemperatureConverter {
private var _celsius: Double = 0
var celsius: Double {
get {
return _celsius
}
set {
_celsius = newValue
}
}
var fahrenheit: Double {
get {
return (_celsius * 9/5) + 32
}
set {
_celsius = (newValue - 32) * 5/9
}
}
}
let temp = TemperatureConverter()
temp.celsius = 25
print(temp.fahrenheit) // Output: 77.0
temp.fahrenheit = 68
print(temp.celsius) // Output: 20.0
In this example, we hide the actual storage variable _celsius
and provide public properties with custom getters and setters.
Private Access with Computed Properties and Methods
You can apply private
access to computed properties and methods as well:
class User {
var firstName: String
var lastName: String
private var fullNameWithPrefix: String {
return "User: \(firstName) \(lastName)"
}
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
func displayInfo() {
print(fullNameWithPrefix)
}
private func generateUserID() -> String {
return "\(firstName.prefix(1))\(lastName)_\(Int.random(in: 100...999))"
}
func createAccount() {
let userID = generateUserID()
print("Account created with ID: \(userID)")
}
}
let user = User(firstName: "John", lastName: "Doe")
user.displayInfo() // Output: User: John Doe
user.createAccount() // Output: Account created with ID: JDoe_123 (random number will vary)
// These would cause compile-time errors:
// print(user.fullNameWithPrefix)
// user.generateUserID()
Private Initializers
You can also make initializers private, which prevents the class from being instantiated outside of its own definition:
class DatabaseManager {
static let shared = DatabaseManager()
// Private initializer ensures only one instance can exist (Singleton pattern)
private init() {
print("Database manager initialized")
}
func connectToDatabase() {
print("Connected to database")
}
}
// Use the shared instance
DatabaseManager.shared.connectToDatabase()
// Output: Connected to database
// This would cause a compile-time error:
// let dbManager = DatabaseManager() // Error: 'init' is inaccessible due to 'private' protection level
This is a common pattern for implementing the Singleton design pattern, ensuring that only one instance of a class can exist.
Private Types
You can also make entire types private:
class NetworkManager {
// Private nested type that can only be used within NetworkManager
private struct RequestConfig {
let timeoutInterval: TimeInterval
let cachePolicy: URLRequest.CachePolicy
}
private let defaultConfig = RequestConfig(
timeoutInterval: 30.0,
cachePolicy: .useProtocolCachePolicy
)
func performRequest(url: URL) {
// Use the private RequestConfig type
var request = URLRequest(url: url)
request.timeoutInterval = defaultConfig.timeoutInterval
request.cachePolicy = defaultConfig.cachePolicy
// Send request...
print("Sending request to \(url) with timeout: \(defaultConfig.timeoutInterval)s")
}
}
let network = NetworkManager()
network.performRequest(url: URL(string: "https://example.com")!)
// Output: Sending request to https://example.com with timeout: 30.0s
// This would cause a compile-time error:
// let config = NetworkManager.RequestConfig(timeoutInterval: 10, cachePolicy: .reloadIgnoringLocalCacheData)
Private vs. fileprivate
While this tutorial focuses on private
, it's worth briefly mentioning fileprivate
, which is less restrictive than private
:
private
: Accessible only within the defining type and its extensions in the same filefileprivate
: Accessible from anywhere within the same source file
class A {
private var privateProperty = "Private"
fileprivate var filePrivateProperty = "File Private"
}
class B {
func accessA() {
let a = A()
// print(a.privateProperty) // Error: 'privateProperty' is inaccessible
print(a.filePrivateProperty) // This works because we're in the same file
}
}
Best Practices
-
Start with private by default: Consider making all properties and methods private by default, then selectively expose what's necessary.
-
Hide implementation details: Use
private
for anything that represents internal implementation details. -
Mark private setters: For properties that should be readable but not modifiable from outside:
class Player {
private(set) var score: Int = 0
func addPoints(_ points: Int) {
score += points
}
}
let player = Player()
player.addPoints(10)
print(player.score) // Output: 10
// player.score = 100 // Error: Cannot assign to property: 'score' setter is inaccessible
- Prefix private properties: Some developers use an underscore prefix for private properties to distinguish them from public interface:
class Person {
private var _age: Int
var age: Int {
return _age
}
init(age: Int) {
self._age = max(0, age) // Ensure age is not negative
}
}
Summary
The private
access control modifier in Swift:
- Restricts access to properties, methods, or types to their defining scope
- Is accessible within the defining type and its extensions in the same file
- Helps implement encapsulation by hiding implementation details
- Is useful for creating helper methods, internal storage properties, and proper APIs
- Prevents external code from directly manipulating internal state
Using private
appropriately leads to more maintainable and less error-prone code by limiting the scope of variables and methods, reducing potential side effects, and creating cleaner interfaces for your types.
Additional Resources
Exercises
-
Create a
Library
class with a private array of books and methods to add, remove, and list books. -
Implement a
Counter
class with a private count variable and methods to increment, decrement, and get the current count. -
Create a
SecurePassword
class that stores a password privately and provides a method to validate a given input against the stored password without revealing the actual password. -
Implement the Singleton pattern for a
LogManager
class using a private initializer. -
Create a
BankAccount
class with private balance and methods for deposit, withdrawal, and checking balance. Include validation to ensure the balance doesn't go below zero.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)