Access Control
Control how parts of your code are visible to other files and modules. Access control lets you conceal internal details and expose only the intended interface for using your code.
You can set access levels for types (like classes, structs, and enums), as well as their properties, methods, initializers, and subscripts. Protocols, global constants, variables, and functions can also be limited to specific contexts.
Swift sets sensible defaults, so for a basic app, you might skip explicit levels entirely. Entities like properties, types, and functions are the building blocks we'll discuss.
Key Concepts: Modules, Files, and Packages
Swift's system revolves around three units:
Module: A self-contained bundle of code, like an app or framework, importable via
import. Each Xcode target counts as one module. Grouping code into a framework creates a reusable module.Source File: A single
.swiftfile inside a module. Files can hold multiple types, functions, etc., but often focus on one for clarity.Package: A collection of modules built together. Define it in tools like Swift Package Manager (
Package.swift) or Xcode settings—not in code.
Access Levels
There are six levels, relative to the file, module, or package:
| Level | Description | Typical Use |
|---|---|---|
| Open | Usable anywhere; allows subclassing/override from other modules. | Public class APIs in frameworks. |
| Public | Usable anywhere, but no subclassing/override from outside the module. | Framework interfaces. |
| Package | Limited to the defining package. | Multi-module apps/frameworks. |
| Internal | Limited to the defining module (default for most entities). | App or framework internals. |
| File-Private | Limited to the defining file. | Hide file-specific details. |
| Private | Limited to the enclosing declaration and same-file extensions. | Hide within a single type. |
Open is least restrictive; private is most. Open applies only to classes/members, signaling safe external extension.
Core Rule
No entity can depend on another with a stricter (lower) access level. For instance:
- A public variable can't use an internal type.
- A function's level can't exceed its parameters or return type.
Defaults
Most entities default to internal. For single-target apps, this suffices—add file-private or private only for hiding details.
For frameworks, mark APIs as open/public; internals stay internal or stricter.
Unit tests can access internals via @testable import (with testing enabled).
Syntax Basics
Prefix declarations with modifiers like public or private:
public class MyPublicClass {} // Public class
internal struct MyInternalStruct {} // Internal struct (default)
private func myPrivateFunction() {} // Private functionOmit modifiers for internal default.
Custom Types
Set levels at definition. A type's level sets defaults for its members:
- Private/file-private types: Members default to private/file-private.
- Internal/public types: Members default to internal (even for public types—explicitly mark public members).
public class MyPublicClass {
public var publicProp = 0 // Explicit public
var internalProp = 0 // Implicit internal
fileprivate func filePrivateFunc() {}
private func privateFunc() {}
}
class MyInternalClass { // Implicit internal
var internalProp = 0 // Implicit internal
fileprivate func filePrivateFunc() {}
private func privateFunc() {}
}
fileprivate class MyFilePrivateClass { // Explicit file-private
func filePrivateFunc() {} // Implicit file-private
private func privateFunc() {}
}
private class MyPrivateClass { // Explicit private
func privateFunc() {} // Implicit private
}Public types need explicit public for members to avoid accidental exposure.
Tuples
A tuple's level is the strictest of its types. E.g., (publicType, privateType) is private.
Functions
Level is the strictest of parameters/return. Explicitly match if needed:
// Won't compile—return is private
func myFunc() -> (MyInternalClass, MyPrivateClass) { ... }
// Fixed
private func myFunc() -> (MyInternalClass, MyPrivateClass) { ... }Enums
Cases match the enum's level; can't differ. Raw/associated values must meet or exceed it.
public enum Direction {
case up, down, left, right // All public
}Nested Types
Match container's level, but public containers default nested to internal (explicit public needed).
Subclassing
Subclass accessible classes in the same module, or open classes cross-module. Subclass level ≤ superclass.
Override visible members same-module, or open members cross-module. Overrides can widen access:
public class Base {
fileprivate func hiddenMethod() {}
}
internal class Derived: Base {
override internal func hiddenMethod() {
super.hiddenMethod() // OK in same file
}
}Constants, Variables, Properties, Subscripts
Can't exceed their type's level. Private types require private entities.
Getters/setters match the entity. Setters can be stricter:
struct Counter {
private(set) var count = 0 // Read internal, write private
var text: String = "" {
didSet { count += 1 }
}
}
var c = Counter()
c.text = "Hello" // Increments count (internally)
print(c.count) // 1 (readable)
// c.count += 1 // Error: setter inaccessibleFor public types, combine modifiers: public private(set) var count = 0.
Initializers
≤ type's level (except required: matches class). Parameters ≥ initializer's level.
Defaults:
- No-arg: Matches type, but internal for public types (provide explicit public if needed).
- Memberwise (structs): Private if any private props; file-private if file-private; else internal.
Protocols
Set at definition. Requirements auto-match protocol's level (can't differ).
Public protocols require public implementations.
public protocol Drawable {
var color: String { get } // Implicit public
func draw()
}
public class Circle: Drawable {
public var color = "Red" // Must be public
public func draw() { ... }
}Inheritance: ≤ inherited protocol's level.
Conformance: Can be stricter than type. Implementation ≥ conformance level.
Extensions
Add members with container's default level. Explicit modifier sets extension default.
Same-file extensions access privates bidirectionally.
For conformance, use protocol's level.
struct Box {
private var secret = 42
}
extension Box: Equatable { // Same file: accesses private
static func ==(lhs: Box, rhs: Box) -> Bool {
return lhs.secret == rhs.secret
}
}Generics
Level = min(of generic, type constraints).
Type Aliases
Treated as distinct types. ≤ aliased type's level.
public typealias SafeInt = Int // OK
// public typealias UnsafeInt = MyPrivateStruct // Error