Exploring Accessor Macros in Swift

VINODH KUMAR
3 min readFeb 24, 2024

--

Photo by Mohammad Rahmani on Unsplash

Swift, celebrated for its versatility and extensibility, offers developers a myriad of tools to enhance code functionality. Among these tools, Accessor Macros stand out as a potent mechanism for dynamically modifying stored properties by adding custom accessors. This article delves into the realm of Accessor Macros, elucidating their significance and demonstrating their utility through practical examples.

Understanding Accessor Macros

Accessor Macros represent a category of macros in Swift specifically designed to augment stored properties by adding custom accessors. These macros are designated with the “accessor” attribute and are implemented by types conforming to the AccessorMacro protocol. By leveraging Accessor Macros, developers can imbue stored properties with custom behavior, thereby enhancing control over property access and manipulation within Swift code.

Example Scenario: Custom Dictionary Storage

Let’s consider a scenario where we need to store data in a dictionary-like structure, but we want to add custom behavior to the stored properties. We can achieve this using the DictionaryStorageProperty macro.

Macro Definition

To define an Accessor Macro, we utilize the “accessor” attribute, signifying its role in adding custom accessors to stored properties. Here’s an example of defining a DictionaryStorageProperty macro:

@attached(accessor)
public macro DictionaryStorageProperty(key: String? = nil) = #externalMacro(module: "DictionaryStorageMacroMacros", type: "DictionaryStoragePropertyMacro")

Macro Implementation

The implementation of the DictionaryStorageProperty macro dynamically adds custom accessors to the stored property, allowing for custom behavior when accessing or modifying the property. Here’s a snippet of the macro implementation:

public struct DictionaryStoragePropertyMacro: AccessorMacro {
public static func expansion(
of node: SwiftSyntax.AttributeSyntax,
providingAccessorsOf declaration: some SwiftSyntax.DeclSyntaxProtocol,
in context: some SwiftSyntaxMacros.MacroExpansionContext
) throws -> [SwiftSyntax.AccessorDeclSyntax] {
// Extract information from the stored property declaration
guard let variableDecl = declaration.as(VariableDeclSyntax.self),
let patternBinding = variableDecl.bindings.as(PatternBindingListSyntax.self)?.first?.as(PatternBindingSyntax.self),
var identifier = patternBinding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text,
let identifierType = patternBinding.typeAnnotation?.as(TypeAnnotationSyntax.self)?.type.as(IdentifierTypeSyntax.self)?.name.text else {
return []
}
// Extract custom key from macro argument if provided
if let attributeSyntax = variableDecl.attributes.as(AttributeListSyntax.self)?.first?.as(AttributeSyntax.self),
let argument = attributeSyntax.arguments?.as(LabeledExprListSyntax.self)?.first?.as(LabeledExprSyntax.self),
let key = argument.expression.as(StringLiteralExprSyntax.self)?.segments.as(StringLiteralSegmentListSyntax.self)?.first?.as(StringSegmentSyntax.self)?.content.text {
identifier = key
}
// Generate custom accessors for the stored property
return [
AccessorDeclSyntax(stringLiteral: """
get {
dictionary["\(identifier)"]! as! \(identifierType)
}
"""),
AccessorDeclSyntax(stringLiteral: """
set {
dictionary["\(identifier)"] = newValue
}
""")
]
}
}

Example Usage

Applying the DictionaryStorageProperty macro to a struct declaration exemplifies its usage:

@DictionaryStorageProperty
struct CustomData {
init(dictionary: [String: Any]) {
self.dictionary = dictionary
}

var dictionary: [String: Any]
var age: Int
}

Expanded Code

After applying the macro, the stored property “age” is augmented with custom accessors:

struct CustomData {
init(dictionary: [String: Any]) {
self.dictionary = dictionary
}

var dictionary: [String: Any]
var age: Int {
get {
dictionary["age"]! as! Int
}
set {
dictionary["age"] = newValue
}
}
}

Conclusion

Accessor Macros in Swift provide a robust mechanism for enhancing stored properties with custom behavior through the addition of accessors. By leveraging Accessor Macros, developers can imbue stored properties with fine-grained control over their access and manipulation, thereby promoting code flexibility, maintainability, and readability. With Accessor Macros, Swift developers can tackle a wide range of scenarios requiring custom property behavior, from data validation to logging and beyond.

--

--

VINODH KUMAR

📱 Senior iOS Developer | Swift Enthusiast | Tech Blogger 🖥️ Connect with me on linkedin.com/in/vinodhkumar-govindaraj-838a85100