A Swift macro that automatically generates observedKeyPaths
and observation readers for SwiftUI and Observation framework integration.
- π Automatically detects observable properties using
@ObservationTracked
or falls back to all non-ignored stored properties - π± Generates
observedKeyPaths
array for type-safe key path access - π Creates observation readers for efficient change tracking
- π Integrates with Combine and ObservableObject for backward compatibility
- β‘ Lightweight and performant with no runtime overhead
- iOS 17.0+ / macOS 10.15+
- Swift 5.10+
- Xcode 15.0+
Add the following dependency to your Package.swift
:
dependencies: [
.package(url: "https://github.com/kramlex/ObservedKeyPathsMacros.git", from: "1.0.0")
]
Or add it directly in Xcode:
- File β Add Package Dependencies
- Enter:
https://github.com/kramlex/ObservedKeyPathsMacros.git
- Click Add Package
Simply add the @GenerateObservedKeyPaths
macro to your Observable class:
import ObservedKeyPaths
import Observation
@Observable
@GenerateObservedKeyPaths
class UserProfile {
var name: String = ""
var email: String = ""
var age: Int = 0
// The macro will automatically generate:
// - observedKeyPaths: [\UserProfile.name, \UserProfile.email, \UserProfile.age]
// - _observationReaders for efficient tracking
// - objectWillChange publisher for Combine compatibility
}
You can explicitly mark which properties to track:
@Observable
@GenerateObservedKeyPaths
class Settings {
@ObservationTracked var theme: String = "light"
@ObservationTracked var fontSize: Int = 14
var internalId: String = "123" // This won't be tracked
// Only theme and fontSize will be included in observedKeyPaths
}
Use @ObservationIgnored
to exclude properties from tracking:
@Observable
@GenerateObservedKeyPaths
class DataModel {
var importantData: String = ""
@ObservationIgnored var cachedData: String = "" // This won't be tracked
@ObservationIgnored var debugInfo: String = "" // This won't be tracked
}
The macro automatically provides Combine compatibility:
@Observable
@GenerateObservedKeyPaths
class Counter {
var count: Int = 0
func increment() {
count += 1
}
}
let counter = Counter()
let cancellable = counter.objectWillChange
.sink { _ in
print("Counter changed!")
}
counter.increment() // Will trigger the publisher
The @GenerateObservedKeyPaths
macro:
- Analyzes your class/struct to find observable properties
- Generates
observedKeyPaths
- an array ofPartialKeyPath<Self>
for type-safe access - Creates observation readers - functions that access each property to trigger observation
- Provides Combine compatibility - integrates with existing ObservableObject patterns
For a class like:
@Observable
@GenerateObservedKeyPaths
class Example {
var value: String = ""
var count: Int = 0
}
The macro generates:
extension Example: ObservationType {}
static let observedKeyPaths: [PartialKeyPath<Example>] = [\Example.value, \Example.count]
static let _observationReaders: [(Example) -> Void] = [{ _ = $0.value as Any }, { _ = $0.count as Any }]
@ObservationIgnored
private lazy var _holder: ObservableObjectPublisherHolder = {
ObservableObjectPublisherHolder(changesPublisherUsingReaders())
}()
@ObservationIgnored
public lazy var objectWillChange: ObservableObjectPublisher = {
_holder.objectWillChange
}()
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with SwiftSyntax
- Inspired by the Swift Observation framework
- Designed for seamless SwiftUI integration