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 moduleopen
: 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:
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:
// 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:
- Creating frameworks or libraries for others to use
- Defining APIs that should be available across module boundaries
- Building reusable components that will be shared across projects
- 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.
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:
// 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
:
// 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:
// 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:
- Public properties that clients can modify
- Private implementation details hidden from users
- Public methods forming a clear API
- A public initializer allowing instance creation
Public Access with Generic Types
Public access works well with generic types too:
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
orinternal
By properly using public access control, you can create clean, maintainable APIs that are easy to use while keeping implementation details properly encapsulated.
Exercises
- Create a public
Logger
class with methods for logging different types of messages (info, warning, error) - Design a public API for a data storage module that allows saving and retrieving user preferences
- Refactor an existing class to have a clean public API while hiding implementation details
- 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! :)