Skip to content

Instantly share code, notes, and snippets.

@megabitsenmzq
Last active July 26, 2023 17:49
Show Gist options
  • Save megabitsenmzq/f3f26d0f8ed512b819f83ee7e30cf713 to your computer and use it in GitHub Desktop.
Save megabitsenmzq/f3f26d0f8ed512b819f83ee7e30cf713 to your computer and use it in GitHub Desktop.
A macro that saves the variable to user defaults.
// Usage
@SavedInDefaults(defaultValue: "foo", group: "group.foo", namespace: "bar")
var fooBar: String
// Expanded ---
{
get {
if let savedValue = UserDefaults(suiteName: "group.foo")!.object(forKey: "bar.fooBar") as? String {
return savedValue
} else {
return "foo"
}
}
set {
UserDefaults(suiteName: "group.foo")!.set(newValue, forKey: "bar.fooBar")
}
}
// ---
// Macro
@attached(accessor)
public macro SavedInDefaults<T>(defaultValue: T, group: String = "", namespace: String = "") = #externalMacro(module: "HSCalculatorMacroMacros", type: "SavedInDefaultsMacro")
// CompilerPlugin
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
public struct SavedInDefaultsMacro: AccessorMacro {
public static func expansion<
Context: MacroExpansionContext,
Declaration: DeclSyntaxProtocol
>(
of node: AttributeSyntax,
providingAccessorsOf declaration: Declaration,
in context: Context
) throws -> [AccessorDeclSyntax] {
guard let varDecl = declaration.as(VariableDeclSyntax.self),
let binding = varDecl.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
binding.accessor == nil,
let type = binding.typeAnnotation?.type
else {
return []
}
var defaults = "UserDefaults.standard"
var namespace = ""
guard case let .argumentList(arguments) = node.argument else {
fatalError("No arguments")
}
if let stringLiteral = arguments.map({$0})[1].expression.as(StringLiteralExprSyntax.self),
case let .stringSegment(newGroupID)? = stringLiteral.segments.first {
let text = newGroupID.content.text
if text != "" {
defaults = "UserDefaults(suiteName: \"\(newGroupID.content.text)\")!"
}
}
if let stringLiteral = arguments.map({$0})[2].expression.as(StringLiteralExprSyntax.self),
case let .stringSegment(newNameSpace)? = stringLiteral.segments.first {
let text = newNameSpace.content.text
if text != "" {
namespace = newNameSpace.content.text + "."
}
}
let defaultValue = arguments.map({$0})[0].expression
return [
"""
get {
if let savedValue = \(raw: defaults).object(forKey: \(literal: namespace + identifier.text)) as? \(type) {
return savedValue
} else {
return \(defaultValue)
}
}
""",
"""
set {
\(raw: defaults).set(newValue, forKey: \(literal: namespace + identifier.text))
}
""",
]
}
}
@main
struct HSCalculatorMacroPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
SavedInDefaultsMacro.self,
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment