Access Control
Access control restricts access to parts of your code from code in other source files and modules. It lets you hide implementation details and expose a clean, deliberate interface. This chapter covers all six access levels and how they apply to types, members, protocols, extensions, and generics.
Modules and source files
Swift's access control model is based on the concepts of modules, source files, and packages.
- A module is a single unit of code distribution — a
framework or app that's built and shipped as one piece and imported with the
importkeyword. Each build target in Xcode is a separate module. - A source file is a single
.swiftfile within a module. It's common to define a single type per file, but a file can contain many types. - A package is a group of modules that you develop together, defined by the build system you use (for example, in your Swift Package Manager configuration).
The access levels
Swift provides six access levels for entities in your code. These levels are relative to the source file in which an entity is defined, and relative to the module — and package — that source file belongs to. From most open to most restrictive:
| Level | Accessible from |
|---|---|
open | Any source file in the defining module, and any module that imports it. Additionally, classes and class members can be subclassed and overridden from outside the module. |
public | Any source file in the defining module, and any module that imports it. Cannot be subclassed/overridden outside the defining module. |
package | Any source file in the same package, but not outside it. |
internal | Any source file in the defining module, but not outside it. This is the default. |
fileprivate | Only within its own defining source file. |
private | Only within the enclosing declaration and extensions of that declaration in the same file. |
open access is the highest (least restrictive) and
private is the lowest (most restrictive). open
applies only to classes and their members, and it differs from
public in that it permits code outside the module to subclass
and override.
Guiding principle and defaults
Access control in Swift follows an overarching guiding principle: no entity can be defined in terms of another entity that has a lower (more restrictive) access level. For example, a public variable can't have a type that's internal, fileprivate, or private — because the type might not be available everywhere the public variable is.
If you don't specify an access level, entities default to
internal. This means that during everyday app development —
where you're working inside a single module — you often don't need to write
any access modifiers at all.
Note: there's one exception to the internal default: a
private or fileprivate top-level entity's default
members follow the enclosing type, and unit-test targets can access internal
entities of a module imported with @testable import.
Custom types
The access level of a type also affects the default access level of that
type's members (its properties, methods, initializers, and
subscripts). If you define a type as private or
fileprivate, its members default to that same level. If you
define a type as internal or public, its members
default to internal — Swift deliberately doesn't make a public
type's members public automatically, so you opt in to your public API
explicitly.
public class SomePublicClass { // explicitly public
public var somePublicProperty = 0 // explicitly public
var someInternalProperty = 0 // implicitly internal
fileprivate func someFileprivateMethod() {} // fileprivate
private func somePrivateMethod() {} // private
}
class SomeInternalClass {} // implicitly internal
fileprivate class SomeFilePrivateClass {} // explicitly fileprivate
private class SomePrivateClass {} // explicitly private
A type's access level sets the ceiling for its members' access.
Tuple and function types
The access level of a tuple type is the most restrictive of all the types used in it. It's deduced automatically; you can't specify it explicitly. The access level of a function type is the most restrictive of its parameter and return types, and you must write it explicitly if the calculated level doesn't match the contextual default:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// must be marked private: the return tuple's most
// restrictive member (SomePrivateClass) is private
return (SomeInternalClass(), SomePrivateClass())
}
A function's level can't exceed the least accessible type it touches.
Enumeration and nested types
The individual cases of an enumeration automatically receive the same access level as the enumeration they belong to — you can't specify a different level for individual cases. The types used for any raw values or associated values must have an access level at least as high as the enumeration's.
Nested types follow the rules for members: a nested type defined within a private type is itself private; within a fileprivate type, fileprivate; and within a public or internal type, internal (unless you explicitly mark it public).
public enum CompassPoint { // all cases are public too
case north
case south
case east
case west
}
Enumeration cases inherit the enum's access level.
Subclassing and members
You can subclass any class that's accessible in the current context and that's
either in the same module (any access level) or marked open (from
another module). A subclass can't have a higher access level than its
superclass. However, you can override an inherited member to make it
more accessible than its superclass version, as long as you
respect the surrounding rules:
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() { // raised to internal
super.someMethod() // OK: superclass member is fileprivate, callable here
}
}
An override can widen access; it can also call a less-accessible superclass member if both live in the allowed scope.
For individual members: a property's getter and setter receive the same access level as the property by default, but you can give the setter a lower level than the getter. This is useful for read-only-from-outside properties:
public struct TrackedString {
public private(set) var numberOfEdits = 0 // public read, private write
public var value: String = "" {
didSet { numberOfEdits += 1 }
}
public init() {}
}
public private(set) exposes the value for reading while keeping mutation internal.
Initializers and protocols
Custom initializers can be assigned an access level less
than or equal to the type they initialize. The one exception is a
required initializer, which must have the same access level
as its class. A default memberwise initializer for a structure is
private if any of the structure's stored properties are private,
and fileprivate if any are fileprivate; otherwise it's
internal. To use a memberwise initializer outside the module, you must
provide a public one yourself.
The access level of a protocol determines the access level of its requirements. Unlike other types, a protocol's requirements automatically take on the same access level as the protocol itself — you can't set a requirement to a different level. A type can conform to a protocol only if the conformance is at least as accessible as the lower of the type's level and the protocol's level.
public protocol Container {
associatedtype Item
var count: Int { get } // implicitly public
mutating func append(_ item: Item) // implicitly public
}
Protocol requirements match the protocol's own access level.
Extensions, generics, and type aliases
You can extend a class, structure, or enumeration in any access context. Members added in an extension have the same default access level as members declared in the original type. You can mark an extension with an explicit access modifier to set a new default for its members, or annotate individual members. An extension that adds protocol conformance can't provide an explicit access modifier — the conformance members automatically take the protocol's access level.
Extensions in the same file as the type they extend behave as though their
code were part of the original declaration. This means a private
member declared in the type is accessible from extensions in the same file,
and vice versa.
The access level for a generic type or function is the minimum of the access level of the type/function itself and the access level of any type constraints on its type parameters. A type alias is treated as a distinct type for access control: it can have an access level less than or equal to the access level of the type it aliases.
private typealias PrivateInt = Int // alias more restrictive than Int — fine
struct Stack<Element> { // internal: min of struct + constraints
private var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element { items.removeLast() }
}
A generic type's access level is capped by its own modifier and any constraints.
Tip: reach for package when you split an app
into several modules that ship together: it lets sibling modules share helper
code without exposing it as part of your shipped public API.
Wrapping up
Access control is how you draw boundaries: private and
fileprivate hide details within a type or file,
internal (the default) keeps things inside a module,
package shares across a package, and public/open
form your shipped API — with open alone permitting outside
subclassing. The guiding principle ties it together: never expose an entity
in terms of something less accessible than itself.