Real projects are split into packages and tested; access control decides what each module exposes.
Swift Package Manager (SwiftPM) builds, tests and resolves dependencies. A package is a folder with a Package.swift manifest, written in Swift, listing products (libraries, executables), targets (modules, one folder each under Sources/ and Tests/) and dependencies (Git URLs with version requirements). Common commands: swift package init, swift build, swift run, swift test. Xcode opens packages directly.
Each target is a module, and access control is defined relative to modules and files:
private— enclosing declaration (and its extensions in the same file).fileprivate— the current file.internal— the whole module (the default).package— any module in the same package (Swift 5.9+).public— visible to importers, but classes can't be subclassed and methods overridden outside the module.open— public plus subclassable and overridable.
Design small public APIs and keep everything else internal.
For tests, Swift Testing (bundled since Swift 6) uses import Testing, functions marked @Test, and the #expect(...) and #require(...) macros, which show the values of sub-expressions on failure. @Test(arguments:) runs a test once per input. Tests run in parallel by default. @testable import MyLib gives tests access to internal declarations. XCTest still works and can live alongside it.
Example
// swift-tools-version: 6.0 (Package.swift; this line comes first)
import PackageDescription
let package = Package(
name: "MathKit",
products: [.library(name: "MathKit", targets: ["MathKit"])],
targets: [
.target(name: "MathKit"),
.testTarget(name: "MathKitTests", dependencies: ["MathKit"]),
]
)
// Sources/MathKit/Math.swift
public func isPrime(_ n: Int) -> Bool {
guard n > 1 else { return false }
var d = 2
while d * d <= n { if n % d == 0 { return false }; d += 1 }
return true
}
// Tests/MathKitTests/MathTests.swift
import Testing
@testable import MathKit
@Test(arguments: [2, 3, 13, 97])
func primes(n: Int) { #expect(isPrime(n)) }
@Test func nonPrimes() { #expect(!isPrime(1) && !isPrime(91)) }Remember
- A package has products, targets (modules) and dependencies in
Package.swift - Default access is
internal;public/openexport across modules - Swift Testing:
@Test,#expect, parameterizedarguments:
Watch out for
- Forgetting that
publicclasses can't be subclassed outside the module unless markedopen.