Skip to main content

Swift Access Guidelines

When designing your Swift code, choosing the right access level is crucial for maintaining clean, maintainable, and secure code. This guide will help you understand Swift's recommended practices for access control so you can make informed decisions in your projects.

Introduction to Access Guidelines

Swift's access control system helps you hide implementation details of your code and specify which parts you want to expose as public interface. Following proper access guidelines allows you to:

  • Enforce clear boundaries between components
  • Prevent unintended modifications to internal data
  • Improve code readability and maintainability
  • Create more robust APIs

Let's explore the recommended practices for Swift access control.

Default to Private Access

The Principle of Least Privilege

When designing your code, it's best to start with the most restrictive access level and only increase access when necessary. This principle, known as "least privilege," enhances security and reduces potential bugs.

swift
class BankAccount {
// Start with private
private var accountNumber: String
private var balance: Double

// Expose only what's needed
public func deposit(amount: Double) {
balance += amount
}

public func withdraw(amount: Double) -> Bool {
if balance >= amount {
balance -= amount
return true
}
return false
}

public func getBalance() -> Double {
return balance
}

init(accountNumber: String, initialBalance: Double) {
self.accountNumber = accountNumber
self.balance = initialBalance
}
}

In this example, we've kept the actual account data private while only exposing the necessary functionality through public methods.

Use Internal for Module-Wide Components

The default internal access is appropriate for components that need to be used throughout your module but shouldn't be exposed to external modules.

swift
// Internal components for use within the module
internal class DatabaseManager {
internal func connect() { /* ... */ }
internal func executeQuery(_ query: String) { /* ... */ }
}

// Public API that uses the internal components
public class UserService {
private let dbManager = DatabaseManager()

public func getUser(id: Int) -> User? {
dbManager.connect()
// Use internal components to implement public functionality
return nil // Simplified for example
}
}

Use Public Only for API Boundaries

Reserve public access for types and members that form the public API of your module or framework.

swift
// Public API for a networking library
public class NetworkClient {
// Public interface
public func send<T: Decodable>(request: URLRequest) async throws -> T {
// Implementation details
return try await performRequest(request)
}

// Internal implementation details
internal func createURLSession() -> URLSession {
// Create and configure session
return URLSession.shared
}

// Private helpers
private func performRequest<T: Decodable>(_ request: URLRequest) async throws -> T {
// Implementation details
fatalError("Not implemented")
}
}

Fileprivate vs Private

Use private when you want to restrict access to the containing type, and fileprivate when multiple types in the same file need to access the property or method.

swift
// File: UserManagement.swift

fileprivate struct UserCredentials {
let username: String
let password: String
}

class UserAuthentication {
// Can access UserCredentials because it's in the same file
func authenticate(credentials: UserCredentials) -> Bool {
// Authentication logic
return true
}
}

class UserRegistration {
// Can also access UserCredentials
func register(username: String, password: String) {
let credentials = UserCredentials(username: username, password: password)
// Registration logic
}
}

Open vs Public for Class Inheritance

When creating a framework or library, consider the difference between open and public for classes:

swift
// In your framework:

// Can be subclassed and overridden by any module
open class BaseViewController {
open func configure() {
// Default implementation
}
}

// Can be used but not subclassed outside the module
public class NetworkUtility {
public func performRequest() {
// Implementation
}
}

When a client imports your framework:

swift
import YourFramework

// This is allowed because BaseViewController is open
class CustomViewController: BaseViewController {
override func configure() {
// Custom implementation
}
}

// This would cause a compilation error since NetworkUtility is only public
// class CustomNetworkUtility: NetworkUtility { }

Practical Guidelines in a Real-World Example

Let's see these guidelines in action with a more comprehensive example of a simple task management system:

swift
// TaskManagement.swift

// Public API - exposed to other modules
public struct Task {
public let id: UUID
public let title: String
public private(set) var isComplete: Bool

public init(title: String) {
self.id = UUID()
self.title = title
self.isComplete = false
}
}

// Public interface of the manager
public class TaskManager {
// Private implementation details
private var tasks: [UUID: Task] = [:]

// Internal helper for testing
internal var taskCount: Int {
return tasks.count
}

// Public API
public init() {}

public func add(task: Task) {
tasks[task.id] = task
}

public func markComplete(taskId: UUID) -> Bool {
guard var task = tasks[taskId] else { return false }

// Internal mutation using private setter
var updatedTask = task
updatedTask.isComplete = true
tasks[taskId] = updatedTask
return true
}

public func getAllTasks() -> [Task] {
return Array(tasks.values)
}

// Fileprivate functionality used by other types in this file
fileprivate func clearCompletedTasks() {
tasks = tasks.filter { !$0.value.isComplete }
}
}

// Only accessible within this file
fileprivate class TaskUtility {
fileprivate func cleanupTasks(manager: TaskManager) {
// Can access fileprivate methods
manager.clearCompletedTasks()
}
}

// Internal type - used within the module but not exposed
internal struct TaskAnalytics {
internal func trackTaskCompletion() {
// Implementation
}
}

This example demonstrates:

  • public types and methods that form the API
  • private properties to hide implementation details
  • internal components used within the module
  • fileprivate methods for sharing code within a file
  • public private(set) for read-only public properties

Here's a quick reference for deciding which access level to use:

  1. Start with private for all properties and methods
  2. Use fileprivate when multiple types in the same file need access
  3. Use internal (default) for components used within your module
  4. Use public for your module's API
  5. Use open for classes that should be subclassable and methods that should be overridable
  6. Consider public private(set) for properties that should be readable but not writable

Best Practices

  1. Design before implementation: Think about your API boundaries before writing code
  2. Hide implementation details: Only expose what's necessary
  3. Group related functionality: Keep related private components in the same file when practical
  4. Document public APIs: Provide clear documentation for all public interfaces
  5. Test public interfaces: Ensure your public API works as expected

Summary

Swift's access control system provides powerful tools for organizing your code and maintaining proper encapsulation. By following the principle of least privilege and only exposing what's necessary, you can create more maintainable, secure, and robust applications.

When designing your Swift classes, structs, and modules, start with restrictive access control and gradually open up only what's needed. This approach leads to cleaner architecture and fewer bugs in the long run.

Additional Resources

Exercises

  1. Refactor an existing class to follow proper access guidelines
  2. Create a small module with clear public API boundaries
  3. Identify instances where fileprivate might be more appropriate than private
  4. Design a class hierarchy using appropriate open and public access levels
  5. Practice using public private(set) to create read-only public properties

Remember that the best code is not only functional but also maintainable and secure. Good access control is a key part of achieving these goals.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)