Skip to main content

Swift Public Access

In Swift, public access grants the most visibility to your types and members, making them accessible from any file within your module and from other modules that import your code. Understanding public access is crucial when you're building frameworks, libraries, or any code that needs to be used by other developers.

Introduction to Public Access Control

Access control in Swift helps you specify what parts of your code should be visible or usable in other files and modules. The public access level is one of several available levels, and it's essential when creating APIs that other developers will use.

Let's explore how public access works in Swift and when you should use it.

Public vs Open Access Levels

Swift actually provides two levels of access for code that needs to be visible across module boundaries:

  • public: Makes entities visible and usable from any source file in its own module, and from any source file in a module that imports the defining module
  • open: Same as public, but also allows classes to be subclassed and methods to be overridden outside the defining module

This distinction is important when designing APIs and class hierarchies.

Declaring Public Types and Members

To declare something as public, simply use the public keyword before its declaration:

swift
public class User {
public var name: String
public var email: String

public init(name: String, email: String) {
self.name = name
self.email = email
}

public func sendEmail(message: String) {
// Implementation here
print("Sending email to \(name): \(message)")
}
}

With this declaration, any code that imports your module can create User instances, access and modify the name and email properties, and call the sendEmail(message:) method.

Understanding Implicit Access Levels

When you don't specify an access level, Swift uses default access levels:

  • For most entities, the default is internal (visible within the module)
  • For class members, the default is internal
  • For nested types, they inherit the access level of their containing type

To make something public, you must explicitly mark it with public.

Practical Example: Building a Network Library

Let's create a simple networking library with public APIs:

swift
// In NetworkModule.swift
public class NetworkManager {
// Public properties
public static let shared = NetworkManager()

// Private properties (not accessible outside this file)
private var baseURL: String = "https://api.example.com"

// Initialize as private to enforce singleton pattern
private init() {}

// Public method that can be called from any module
public func fetchData(from endpoint: String, completion: @escaping (Data?, Error?) -> Void) {
// Implementation details
let url = URL(string: baseURL + endpoint)!

URLSession.shared.dataTask(with: url) { data, response, error in
completion(data, error)
}.resume()
}

// Internal method (accessible only within this module)
func logRequest(_ request: URLRequest) {
// Implementation details
}
}

// In another module after importing NetworkModule
// We can use:
let networkManager = NetworkManager.shared
networkManager.fetchData(from: "/users") { data, error in
// Process data
}

// But we cannot access:
// networkManager.baseURL (private)
// networkManager.logRequest(_:) (internal)

In this example, we've created a NetworkManager class with:

  • A public shared instance
  • Private implementation details
  • Public methods that form our API
  • Internal helper methods

When to Use Public Access

You should use public access when:

  1. Creating frameworks or libraries for others to use
  2. Defining APIs that should be available across module boundaries
  3. Building reusable components that will be shared across projects
  4. Working on large projects with clear module separation

Public Access Best Practices

When designing public APIs, follow these best practices:

1. Be selective about what you make public

Only expose what's necessary for your API's functionality. Keep implementation details hidden with more restrictive access levels.

swift
public class PaymentProcessor {
// Public API
public func processPayment(amount: Double) -> Bool {
// Call internal methods to do the actual work
return validatePayment(amount) && performTransaction(amount)
}

// Implementation details
private func validatePayment(_ amount: Double) -> Bool {
// Validation logic
return amount > 0
}

private func performTransaction(_ amount: Double) -> Bool {
// Transaction logic
return true
}
}

2. Use public for API stability

When you make something public, changing it later could break client code. Be careful about what you expose:

swift
// More stable API design
public struct APIClient {
public let apiVersion: String

// Implementation details hidden
private var authToken: String?

public init(apiVersion: String) {
self.apiVersion = apiVersion
}

public func performRequest(_ request: Request) -> Response {
// Implementation
return Response()
}
}

3. Consider using open for classes meant to be subclassed

If you're designing a class that others should be able to inherit from and override methods, use open instead of public:

swift
// This class can be subclassed in other modules
open class BaseViewController {
open func viewDidLoad() {
// Default implementation
setupUI()
}

// This can be overridden in subclasses
open func setupUI() {
// Default implementation
}

// This can't be overridden in other modules
public func logViewAppearance() {
// Implementation
}
}

Real-World Application: Building a UI Component Library

Let's look at a more complete example of using public access in a UI component library:

swift
// CustomButton.swift in ButtonKit module
public class CustomButton {
// Public properties that can be accessed and modified
public var title: String {
didSet {
updateButtonTitle()
}
}

public var isEnabled: Bool = true {
didSet {
button.isEnabled = isEnabled
}
}

// Private UI components
private let button = UIButton()

// Public initializer
public init(title: String) {
self.title = title
setupButton()
}

// Public methods
public func setTheme(primaryColor: UIColor, textColor: UIColor) {
button.backgroundColor = primaryColor
button.setTitleColor(textColor, for: .normal)
}

// Event handling with public method
public func addTapHandler(_ handler: @escaping () -> Void) {
// Store the handler and connect to button tap
}

// Private helpers
private func setupButton() {
button.layer.cornerRadius = 5
updateButtonTitle()
}

private func updateButtonTitle() {
button.setTitle(title, for: .normal)
}
}

// In client code:
import ButtonKit

let loginButton = CustomButton(title: "Log In")
loginButton.setTheme(primaryColor: .blue, textColor: .white)
loginButton.addTapHandler {
// Handle button tap
authenticate()
}

// Later, can update properties
loginButton.isEnabled = false // Disable during loading

This example demonstrates:

  1. Public properties that clients can modify
  2. Private implementation details hidden from users
  3. Public methods forming a clear API
  4. A public initializer allowing instance creation

Public Access with Generic Types

Public access works well with generic types too:

swift
public struct Stack<Element> {
private var items = [Element]()

public init() {}

public mutating func push(_ item: Element) {
items.append(item)
}

public mutating func pop() -> Element? {
return items.isEmpty ? nil : items.removeLast()
}

public var top: Element? {
return items.last
}

public var isEmpty: Bool {
return items.isEmpty
}

public var count: Int {
return items.count
}
}

// Client code can use this with any type
let numberStack = Stack<Int>()
numberStack.push(5)
numberStack.push(10)
print(numberStack.top ?? 0) // Outputs: 10

Summary

Public access control in Swift allows you to create well-defined APIs that can be used across module boundaries. Key points to remember:

  • Use public for types and members that should be accessible from other modules
  • Use open for classes that should be subclassable from other modules
  • Be selective about what you make public to maintain flexibility
  • Public APIs form a contract with users, so think carefully about what you expose
  • Hide implementation details using more restrictive access levels like private or internal

By properly using public access control, you can create clean, maintainable APIs that are easy to use while keeping implementation details properly encapsulated.

Exercises

  1. Create a public Logger class with methods for logging different types of messages (info, warning, error)
  2. Design a public API for a data storage module that allows saving and retrieving user preferences
  3. Refactor an existing class to have a clean public API while hiding implementation details
  4. Build a small framework with at least two public types that interact with each other

Additional Resources

Understanding public access control is key to creating well-designed Swift APIs and frameworks that others can easily use and integrate into their projects.



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