Skip to main content

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."

swift
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 and balance are private, meaning they can only be accessed within the BankAccount class
  • We provide public methods deposit() and getBalance() 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:

swift
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:

swift
// 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

swift
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

swift
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

swift
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:

swift
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:

swift
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:

swift
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 file
  • fileprivate: Accessible from anywhere within the same source file
swift
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

  1. Start with private by default: Consider making all properties and methods private by default, then selectively expose what's necessary.

  2. Hide implementation details: Use private for anything that represents internal implementation details.

  3. Mark private setters: For properties that should be readable but not modifiable from outside:

swift
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
  1. Prefix private properties: Some developers use an underscore prefix for private properties to distinguish them from public interface:
swift
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

  1. Create a Library class with a private array of books and methods to add, remove, and list books.

  2. Implement a Counter class with a private count variable and methods to increment, decrement, and get the current count.

  3. 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.

  4. Implement the Singleton pattern for a LogManager class using a private initializer.

  5. 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! :)