---
title: "Swift Modules: SPM Layout and Access Control"
slug: "swift-modules"
category: "coding"
tags: ["swift", "spm", "access-control", "swift-package-manager"]
status: "stable"
last_updated: 2026-05-14
summary: "Swift Package Manager layout, import semantics, private-by-default access levels, and resource handling for Swift modules."
related: ["[[coding/swift]]", "[[coding/swift-protocols]]", "[[coding/swift-value-types]]", "[[ios/swiftui]]", "[[ios/core-data]]"]
---

## Overview

A Swift module is the unit of compilation and access control. Everything in a module shares `internal` visibility by default; only explicitly `public` declarations are visible outside. This page covers Swift Package Manager (SPM) target layout, import semantics, access level rules, `@_implementationOnly`, and resource bundles. For the top-level Swift rules, see [[coding/swift]].

## Structure packages by feature, not by layer

Organize an SPM package so each target owns a vertical slice of functionality, not a horizontal layer (models, services, views). A feature target can be compiled, tested, and replaced independently.

```
MyApp/
├── Package.swift
├── Sources/
│   ├── AppCore/           # domain types shared across features
│   ├── ProfileFeature/    # profile UI, view model, domain logic
│   ├── FeedFeature/       # feed UI, view model, domain logic
│   └── NetworkClient/     # HTTP layer; depends on AppCore
└── Tests/
    ├── ProfileFeatureTests/
    └── FeedFeatureTests/
```

Each target declares its `dependencies` in `Package.swift`. A feature that does not depend on another feature's target cannot accidentally import its internals. This is the module boundary you want to enforce.

## Use `private` as the default access level

Start every declaration at `private`. Open up to `fileprivate`, `internal`, or `public` only when a caller actually requires the symbol. The compiler catches access violations; be explicit rather than relying on convention.

```swift
// private: only this type can call it
private func validateCredentials(_ creds: Credentials) -> Bool { ... }

// fileprivate: a helper type in the same file needs it
fileprivate func sharedFormatting(date: Date) -> String { ... }

// internal (the default): other files in this module
func buildRequest(for endpoint: Endpoint) -> URLRequest { ... }

// public: exported to other modules
public func configure(with settings: AppSettings) { ... }
```

Spell out `internal` on declarations you review in diffs; silent defaults disappear in code review.

## Import only what you need; prefer granular imports

Swift resolves `import Module` by importing all public symbols. For large modules, a more specific import reduces build system dependencies and makes the dependency graph legible.

```swift
// Importing an entire framework when only one type is needed
import Foundation  // pulls in hundreds of types

// Preferred when you only need one type (Swift 5.9+)
import struct Foundation.Date
import class Foundation.URLSession
```

Granular imports are available in Swift 5.9 and later. Use them in library targets to make the dependency surface explicit. In application targets, the ergonomic cost may outweigh the benefit; use your judgment.

## Use `@_implementationOnly` to hide internal dependencies

When a target uses another module internally but does not want to expose that module on its public API surface, `@_implementationOnly import` prevents re-exporting.

```swift
// In a library target: CryptoKit is an implementation detail, not API
@_implementationOnly import CryptoKit

public func hash(_ data: Data) -> String {
    let digest = SHA256.hash(data: data)
    return digest.map { String(format: "%02x", $0) }.joined()
}
```

Callers of the library do not need to import `CryptoKit`. The `@_implementationOnly` attribute is technically unofficial but is widely used and stable in practice. Swift 5.9 introduced `internal import` as the official replacement; prefer `internal import` on new code targeting Swift 5.9 or later.

## Handle module resources through the bundle accessor

SPM generates a `Bundle.module` accessor for targets that declare resources in `Package.swift`. Always use it instead of `Bundle.main` or a hardcoded bundle identifier.

```swift
// Package.swift resource declaration
.target(
    name: "ProfileFeature",
    resources: [.process("Resources")]
)

// Runtime access
let image = UIImage(named: "avatar-placeholder", in: .module, compatibleWith: nil)
let url = Bundle.module.url(forResource: "sample-data", withExtension: "json")
```

`Bundle.main` fails when the target runs in a Swift Playgrounds app, a test host, or a Swift macro plugin. `Bundle.module` always resolves to the correct bundle regardless of the hosting context.

## Limit `open` to types designed for subclassing

`open` allows subclassing and override from outside the module. It is rarely the right choice: it commits the module to a stable superclass contract and enables callers to replace any behavior.

- Use `open` only on a type explicitly designed as an extension point for other modules.
- Use `public` for types that are visible externally but should not be subclassed outside the module.
- Mark non-final `public` classes with a comment explaining why subclassing is intentionally allowed within the module.

Virtually all [[ios/swiftui]] usage and most networking code never needs `open`. Prefer protocol conformances and view modifiers over subclassing entirely.

## Pin minimum versions and test on the supported matrix

A `Package.swift` manifest that omits a `swiftLanguageVersions` setting inherits the toolchain default. Declare the versions you support explicitly to prevent silent behavior changes during upgrades.

```swift
// Package.swift
let package = Package(
    name: "ProfileFeature",
    platforms: [.iOS(.v17), .macOS(.v14)],
    ...
)
```

Test targets inherit the same access control rules as library targets. Internal types are accessible to test targets declared in the same package via `@testable import`, which grants `internal` access to the test bundle.

## Related

- [[coding/swift]]
- [[coding/swift-protocols]]
- [[coding/swift-value-types]]
- [[coding/swift-actors]]
- [[ios/swiftui]]
- [[ios/core-data]]
