TL;DR: The package access modifier in Swift allows developers to share APIs between multiple Targets within the same Package while keeping them invisible to the outside world. It effectively solves the pain point where internal is too restrictive and public is too broad, making it a standard practice for building complex Swift Package Manager (SPM) dependency trees.
Introduced in Swift 5.9, the package access control modifier is designed to limit the scope of an API to a single Package (containing multiple Targets), preventing over-exposure via public. This feature is particularly useful in modular architectures where implementation details need to be shared across multiple Targets but should not be accessible to external consumers, thereby enhancing encapsulation and safety.
Background
As modular programming becomes ubiquitous, developers increasingly lean on the Swift Package Manager (SPM) to split distinct features into separate Packages. Furthermore, a single Package is often subdivided into multiple Targets to organize code better. In this structure, restricting API usage to within the Package (across different Targets) without exposing it via public is a common requirement.
The package Keyword
The package keyword specifies that an API is visible only to different Targets within the same Package, eliminating the need to expose internal implementations to external modules. This fills the critical gap between internal (visible only within the specific module/target) and public (completely open to everyone).
public struct MyStruct {
public init() { ... }
public var name: String { ... }
// Accessible only to other Targets within the same Package; invisible externally
package func action() { ... }
}
Real-World Use Case
Consider a Persistent Package in a project, which handles all data persistence logic and is split into multiple Targets for better separation of concerns:
Project
├── Domain
│ └── Package.swift
└── Persistent
├── Package.swift
└── Sources
├── Models (Target)
├── CURD (Target)
└── Stack (Target)
In the Domain Package, a thread-safe ViewModel conversion protocol is defined:
public protocol ViewModelConvertible {
associatedtype Value: ViewModelProtocol
@MainActor
func convertToViewModel() -> Value
}
Since NSManagedObject (Core Data) is not thread-safe, strictly adhering to @MainActor might not be feasible when processing ViewModel conversions internally within the Persistent Package. Therefore, we can define the following protocol in any Target within Persistent:
package protocol ViewModelConvertibleUnsafe {
associatedtype Value: ViewModelProtocol
// Unsafe interface for internal Package use only
func convertToViewModelUnsafe() -> Value
}
By using the package modifier, this protocol is shared exclusively among the Targets within the Persistent Package. This ensures the flexibility of internal logic while maintaining the safety of the external interface. External modules continue to interact via the thread-safe convertToViewModel() interface, oblivious to the unsafe internal workings.