harlanhaskins/assertions
A Swift library for coordinating access to shared resources through an automatically-invalidating assertion system.
Overview
The Assertions library provides a resource management system where multiple components can request access to shared resources. The system automatically combines these requests into a single resolved state using customizable reduction strategies.
Installation
Swift Package Manager
dependencies: [
.package(url: "https://github.com/harlanhaskins/Assertions.git", from: "1.0.0")
]Minimum Requirements
- iOS 17.0+ / macOS 14.0+ / visionOS 2.0+ / tvOS 17.0+ / watchOS 10.0+
- Swift 6.0+
Quick Start
import Assertions
// Create a manager that tracks access to the sidebar. If any client is requesting the sidebar closed,
// it will be closed.
let sidebarManager = AssertionManager<Bool>("SidebarClosed")
// Make a request to close the sidebar.
let assertion = sidebarManager.acquireAssertion(reason: "User preference", value: true)
print(sidebarManager.value) // true
// Release the request (will also happen automatically when `assertion` goes out of scope)
assertion.invalidate()
print(sidebarManager.value) // falseCore Concepts
AssertionManager
The central coordinator that manages all assertions for a specific resource type:
let manager = AssertionManager<Bool>("ResourceName")Assertion
A non-copyable struct representing a single request for resource access:
let assertion = manager.acquireAssertion(reason: "Debug info", value: true)
// Assertion automatically invalidates when deallocatedAssertionValue
Protocol defining how multiple assertion values are combined:
extension Bool: AssertionValue {
static var defaultValue: Bool { false }
static func reduce(into partialResult: inout Bool, nextValue: Bool) {
partialResult = partialResult || nextValue // OR logic
}
}Examples
1. UI State Management
Coordinate multiple UI components that affect shared state:
class ViewController {
private let loadingManager = AssertionManager<Bool>("LoadingState")
private var networkAssertion: Assertion?
private var dataAssertion: Assertion?
func startNetworkRequest() {
networkAssertion = loadingManager.acquireAssertion(
reason: "Network request",
value: true
)
print("Loading: \(loadingManager.value)") // true
}
func startDataProcessing() {
dataAssertion = loadingManager.acquireAssertion(
reason: "Data processing",
value: true
)
print("Loading: \(loadingManager.value)") // true
}
func finishNetworkRequest() {
networkAssertion?.invalidate()
networkAssertion = nil
// Still loading if data processing is active
print("Loading: \(loadingManager.value)") // depends on dataAssertion
}
}2. SwiftUI Integration
Use with SwiftUI for reactive UI updates:
@Observable
class AppState {
let modalManager = AssertionManager<Bool>("ModalPresented")
let busyManager = AssertionManager<Bool>("AppBusy")
}
struct ContentView: View {
@State private var appState = AppState()
var body: some View {
VStack {
if appState.modalManager.value {
Text("Modal is presented")
}
if appState.busyManager.value {
ProgressView("Loading...")
}
}
}
}3. Priority Management
Implement custom value types with sophisticated reduction logic:
enum Priority: AssertionValue {
case low, medium, high, critical
static var defaultValue: Priority { .low }
static func reduce(into partialResult: inout Priority, nextValue: Priority) {
if nextValue.rawValue > partialResult.rawValue {
partialResult = nextValue
}
}
}
let priorityManager = AssertionManager<Priority>("TaskPriority")
let backgroundTask = priorityManager.acquireAssertion(
reason: "Background sync",
value: .low
)
let userAction = priorityManager.acquireAssertion(
reason: "User interaction",
value: .high
)
print(priorityManager.value) // .high (highest priority wins)4. Audio Session Management
Coordinate multiple audio sources:
struct AudioConfiguration: AssertionValue {
let category: String
let volume: Float
static var defaultValue: AudioConfiguration {
AudioConfiguration(category: "ambient", volume: 0.5)
}
static func reduce(into partialResult: inout AudioConfiguration, nextValue: AudioConfiguration) {
// Use highest volume and most restrictive category
partialResult.volume = max(partialResult.volume, nextValue.volume)
if nextValue.category == "playback" {
partialResult.category = nextValue.category
}
}
}
let audioManager = AssertionManager<AudioConfiguration>("AudioSession")
class MusicPlayer {
private var audioAssertion: Assertion?
func startPlayback() {
audioAssertion = audioManager.acquireAssertion(
reason: "Music playback",
value: AudioConfiguration(category: "playback", volume: 0.8)
)
}
func stopPlayback() {
audioAssertion?.invalidate()
audioAssertion = nil
}
}5. Feature Flag System
Manage dynamic feature enabling with multiple conditions:
struct FeatureFlags: AssertionValue {
let experimentalUI: Bool
let debugMode: Bool
let betaFeatures: Bool
static var defaultValue: FeatureFlags {
FeatureFlags(experimentalUI: false, debugMode: false, betaFeatures: false)
}
static func reduce(into partialResult: inout FeatureFlags, nextValue: FeatureFlags) {
// OR logic for all flags
partialResult.experimentalUI = partialResult.experimentalUI || nextValue.experimentalUI
partialResult.debugMode = partialResult.debugMode || nextValue.debugMode
partialResult.betaFeatures = partialResult.betaFeatures || nextValue.betaFeatures
}
}
let flagManager = AssertionManager<FeatureFlags>("AppFeatures")
// Enable experimental UI for beta users
let betaAssertion = flagManager.acquireAssertion(
reason: "Beta user",
value: FeatureFlags(experimentalUI: true, debugMode: false, betaFeatures: true)
)
// Enable debug mode for developers
let debugAssertion = flagManager.acquireAssertion(
reason: "Debug build",
value: FeatureFlags(experimentalUI: false, debugMode: true, betaFeatures: false)
)
print(flagManager.value)
// FeatureFlags(experimentalUI: true, debugMode: true, betaFeatures: true)Advanced Usage
Manual Assertion Management
let manager = AssertionManager<Bool>("ManualExample")
// Create assertion with explicit lifecycle
let assertion = manager.acquireAssertion(reason: "Manual control", value: true)
// Check if assertion is still valid
if assertion.isValid {
print("Assertion active: \(manager.value)")
}
// Manually invalidate when done
assertion.invalidate()
print("After invalidation: \(manager.value)")Delegate Pattern
For synchronous updates instead of Observable:
class MyDelegate: AssertionManagerDelegate {
func assertionManager<Value>(_ manager: AssertionManager<Value>, didUpdateValue value: Value) {
print("Value updated to: \(value)")
}
}
let delegate = MyDelegate()
let manager = AssertionManager<Bool>("Delegated")
manager.delegate = delegate
let assertion = manager.acquireAssertion(reason: "Delegate test", value: true)
print("After assertion granted")
// Prints: "Value updated to: true" then "After assertion granted"Custom Reduction Strategies
struct MinMaxValue: AssertionValue {
let min: Int
let max: Int
static var defaultValue: MinMaxValue {
MinMaxValue(min: 0, max: 100)
}
static func reduce(into partialResult: inout MinMaxValue, nextValue: MinMaxValue) {
partialResult.min = Swift.min(partialResult.min, nextValue.min)
partialResult.max = Swift.max(partialResult.max, nextValue.max)
}
}Debugging
The library includes helpful debugging features:
let manager = AssertionManager<Bool>("DebugExample")
let assertion1 = manager.acquireAssertion(reason: "First reason", value: true)
let assertion2 = manager.acquireAssertion(reason: "Second reason", value: false)
print(manager.description)
// Output includes all active assertions with their reasons and UUIDsThread Safety
AssertionManager is designed for use on the main thread. For concurrent access, wrap operations in appropriate synchronization:
await MainActor.run {
let assertion = manager.acquireAssertion(reason: "Background task", value: true)
}Best Practices
- Use descriptive reasons: They help with debugging and understanding resource usage
- Prefer automatic cleanup: Let assertions invalidate on deallocation when possible
- Keep assertions local: Store them as properties of the component that needs the resource
- Use Observable: Leverage SwiftUI integration for reactive updates
- Test reduction logic: Ensure your custom
AssertionValuetypes behave correctly
License
This project is licensed under the MIT License. See LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Package Metadata
Repository: harlanhaskins/assertions
Default branch: main
README: README.md