TL;DR: Swift 5.9 introduces the
package
access modifier, allowing APIs to be shared across Targets within the same package without exposing them publicly. This supports safer and more modular Swift code by enabling internal sharing while preserving external encapsulation—ideal for designs like multi-Target packages that require internal-only protocols or methods.
Background
With the rise of modular programming, developers are increasingly using Swift Package Manager (SPM) to separate functionalities into multiple packages. Within a single package, it’s common to further divide code into multiple Targets. A recurring challenge in this structure is limiting API access to within the package (including between Targets) without making them public
, which would expose them outside the package.
Introducing the package Access Modifier
As of Swift 5.9, the language introduces the package
access control level. This modifier allows APIs to be shared across different Targets within the same package, without exposing them externally.
public struct MyStruct {
public init() { ... }
public var name: String { ... }
package func action() { ... } // Accessible only within the same package
}
Real-World Use Case: Persistent Package Design
Consider a project that includes a Persistent
package for all data persistence logic, split into multiple Targets:
Project
├── Domain
│ └── Package.swift
└── Persistent
├── Package.swift
└── Sources
├── Models (Target)
├── CURD (Target)
└── Stack (Target)
In the Domain
package, a protocol defines a thread-safe method to convert a model to a ViewModel:
public protocol ViewModelConvertible {
associatedtype Value: ViewModelProtocol
@MainActor
func convertToViewModel() -> Value
}
Since NSManagedObject
is not thread-safe, some conversions need to happen in the thread of the NSManagedObjectContext
, making @MainActor
unsuitable. In such cases, a separate internal protocol can be defined using the package
modifier—in any Target within the Persistent
package.
package protocol ViewModelConvertibleUnsafe {
associatedtype Value: ViewModelProtocol
func convertToViewModelUnsafe() -> Value
}
This keeps the unsafe conversion method available across Targets inside the Persistent
package, while hiding it from external packages. Consumers interact only with the safe, @MainActor
-scoped API.