Skip to content

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

ModifierDescription
openAccessible and subclassable from any module.
publicAccessible from any module, not subclassable outside the module.
internalAccessible within the same module (default).
fileprivateAccessible within the same file.
privateAccessible 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"
}

Released under the MIT License.