The @Diffable
macro provides an efficient way to calculate differences between two instances of a type by automatically generating code to compare properties. It ensures that you only focus on the high-level design of your types while leveraging the macro for advanced functionality.
To use the @Diffable
macro, the target type must:
- Be a
class
,struct
, orenum
. - Conform to the
Equatable
protocol.
The macro will generate an optimized implementation of difference computation for all Equatable
properties in your type.
When applied, the macro generates a computeDifference method that calculates the difference between two Config instances, returning a strongly-typed result indicating the changed properties.
@Diffable
public struct Config: Equatable {
public var name: String
public var id: UUID
}
// expandedSource
public struct Config: Equatable {
public var name: String
public var id: UUID
public struct Difference: OptionSet {
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
static let name = Difference(rawValue: 1 << 0)
static let id = Difference(rawValue: 1 << 1)
}
public func computeDifference(from other: Self) -> Difference {
var difference: Difference = []
var currentCopy: Self? = self
var otherCopy: Self? = other
if currentCopy?.name != otherCopy?.name {
difference.insert(.name)
}
if currentCopy?.id != otherCopy?.id {
difference.insert(.id)
}
currentCopy = nil
otherCopy = nil
return difference
}
}
// usage
let configOne = Config(name: "HEssam", id: UUID())
let configTwo = Config(name: "Alfred", id: UUID())
let differences = configOne.computeDifference(from: configTwo)
/// A custom UIView that displays a person's name and age.
/// This view uses a diffable configuration to detect and apply changes to its properties efficiently.
final class PersonView: UIView {
// MARK: - UI Elements
/// Label to display the person's name.
private let nameLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .headline)
return label
}()
/// Label to display the person's age.
private let ageLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .subheadline)
return label
}()
// MARK: - Properties
/// The current configuration of the view.
private var configuration: Configuration
// MARK: - Initializers
/// Initializes the view with the given configuration.
///
/// - Parameter configuration: The initial configuration of the view.
init(configuration: Configuration) {
self.configuration = configuration
super.init(frame: .zero)
}
@available(*, unavailable, message: "init(frame:) is not supported. Use init(configuration:) instead.")
override init(frame: CGRect) {
fatalError("init(frame:) has not been implemented")
}
@available(*, unavailable, message: "init(coder:) is not supported. Use init(configuration:) instead.")
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Configuration Update
/// Updates the view's configuration and applies changes to the UI.
///
/// - Parameter configuration: The new configuration to apply.
func updateConfiguration(to configuration: Configuration) {
// Compute the differences between the old and new configurations.
let differences = self.configuration.computeDifference(from: configuration)
// Iterate through potential differences and update the UI for changed properties.
for i in 0 ..< Int.bitWidth {
let bit = 1 << i
let difference = Configuration.Difference(rawValue: bit)
if differences.contains(difference) {
switch difference {
case .age:
// Update the age label if the age has changed.
self.ageLabel.text = "\(configuration.age)"
case .name:
// Update the name label if the name has changed.
self.nameLabel.text = configuration.name
default:
break
}
}
}
// Update the stored configuration.
self.configuration = configuration
}
// MARK: - Configuration Struct
/// Represents the configuration of the `PersonView`.
/// This struct uses the `@Diffable` macro to enable efficient change detection.
@Diffable
struct Configuration: Equatable {
/// The person's name.
let name: String
/// The person's age.
let age: Int
}
}
The DiffableMacroError
enum provides detailed error cases to help developers debug issues when applying the macro.
- The macro is applied to an unsupported type. Only class, struct, or enum types are supported.
- The type does not conform to Equatable, which is required for the macro to function.
We warmly welcome contributions to the BuildableMacro project! Whether you're fixing bugs, improving the documentation, or adding new features, your help is appreciated. Here’s how you can contribute:
- Fork the Repository: Start by forking the repository to your own GitHub account.
- Create a Branch: Make your changes in a new branch.
- Make Your Changes: Whether it's a new feature or a bug fix, your contributions make a difference.
- Write Tests: Ensure your changes are working as expected.
- Submit a Pull Request: Once you're satisfied, submit a pull request for review.
To give clarity of what is expected of our members, we have adopted the code of conduct defined by the Contributor Covenant. This document is used across many open source communities. For more, see the Code of Conduct.
Please check LICENSE for details.