Skip to content

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 .swift file 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:

LevelDescriptionTypical Use
OpenUsable anywhere; allows subclassing/override from other modules.Public class APIs in frameworks.
PublicUsable anywhere, but no subclassing/override from outside the module.Framework interfaces.
PackageLimited to the defining package.Multi-module apps/frameworks.
InternalLimited to the defining module (default for most entities).App or framework internals.
File-PrivateLimited to the defining file.Hide file-specific details.
PrivateLimited 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:

swift
public class MyPublicClass {}          // Public class
internal struct MyInternalStruct {}    // Internal struct (default)
private func myPrivateFunction() {}    // Private function

Omit 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).
swift
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:

swift
// 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.

swift
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:

swift
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:

swift
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 inaccessible

For 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.

swift
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.

swift
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.

swift
public typealias SafeInt = Int  // OK
// public typealias UnsafeInt = MyPrivateStruct  // Error

Released under the MIT License.