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.

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:

LevelAccessible from
openAny 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.
publicAny source file in the defining module, and any module that imports it. Cannot be subclassed/overridden outside the defining module.
packageAny source file in the same package, but not outside it.
internalAny source file in the defining module, but not outside it. This is the default.
fileprivateOnly within its own defining source file.
privateOnly 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.