Access Control
Access control restricts the visibility of code entities, promoting encapsulation and modularity. Swift provides five access levels to control access within and across modules.
Access Levels
Modifier | Description |
---|---|
open | Accessible and subclassable from any module. |
public | Accessible from any module, not subclassable outside the module. |
internal | Accessible within the same module (default). |
fileprivate | Accessible within the same file. |
private | Accessible within the enclosing type or same-file extensions. |
Example: Access Levels:
swift
open class Library {
public var name: String
internal var books: [String] = []
fileprivate var staffCount: Int = 0
private var budget: Double = 0.0
init(name: String) {
self.name = name
}
fileprivate func hire() {
staffCount += 1
}
private func allocateFunds(_ amount: Double) {
budget += amount
}
}
class Branch: Library {
func manage() {
hire() // Accessible (fileprivate)
// allocateFunds(1000.0) // Error: 'allocateFunds' is inaccessible
}
}
let library = Library(name: "City Library")
print(library.name) // "City Library"
// print(library.budget) // Error: 'budget' is private
Access Control for Members
Members inherit the type’s access level unless restricted further.
Example:
swift
public struct User {
public let id: String
private var password: String
init(id: String, password: String) {
self.id = id
self.password = password
}
public func verify(_ input: String) -> Bool {
return input == password
}
}
Extensions and Access
Extensions cannot increase access levels beyond the original type.
Example:
swift
extension Library {
func getStaffCount() -> Int {
return staffCount // Accessible (fileprivate)
}
}
Protocols and Access
Protocols define access for their requirements; conformance respects type access.
Example:
swift
public protocol Readable {
func read() -> String
}
internal struct Document: Readable {
func read() -> String {
return "Content"
}
}
Getters and Setters
Specify different access levels for getters/setters.
Example:
swift
struct Config {
private(set) public var version: String
mutating func updateVersion(_ newVersion: String) {
version = newVersion
}
}
var config = Config(version: "1.0")
print(config.version) // "1.0"
// config.version = "2.0" // Error: 'version' setter is private
config.updateVersion("2.0")
Module and Framework Considerations
- Internal` Default: Suitable for app modules.
- Public/Open: Required for framework APIs.
- File Organization: Use
fileprivate
for file-scoped helpers.
Example: Framework:
swift
// In a framework
open class APIClient {
public func fetch() {}
internal func configure() {}
}
Best Practices
- Minimize Exposure: Default to
private
, escalate as needed. - Use
private(set)
: For read-only properties. - Fileprivate for Helpers: Group related code in one file.
- Open for Extensibility: Use in frameworks for subclassing.
- Internal for Modules: Hide implementation details.
- Test Access: Verify access levels in public APIs.
Troubleshooting
- Inaccessible Member: Check access level and module boundaries.
- Extension Errors: Ensure extension respects type access.
- Public API Exposure: Avoid exposing internal types.
- Subclassing Issues: Use
open
for framework classes. - Testing Private Members: Use
@testable
or public APIs for testing.
Example: Comprehensive Access Control Usage
swift
public protocol Resource {
var id: String { get }
}
open class Database {
private var records: [String: Any] = [:]
fileprivate var connection: Connection?
public(set) var isConnected: Bool = false
public func connect() {
connection = Connection()
isConnected = true
}
internal func store(_ resource: Resource) {
records[resource.id] = resource
}
}
fileprivate struct Connection {
func execute() {
print("Executing query")
}
}
internal struct Record: Resource {
let id: String
let data: String
}
extension Database {
func fetchRecord(id: String) -> Resource? {
return records[id] as? Resource
}
}
let db = Database()
db.connect()
let record = Record(id: "r1", data: "Data")
db.store(record)
if let fetched = db.fetchRecord(id: "r1") as? Record {
print(fetched.data) // "Data"
}