Ας είμαστε ειλικρινείς: τα Swift Macros ήταν ίσως η πιο ενδιαφέρουσα προσθήκη στη Swift 5.9, και από τότε έχουν εξελιχθεί σε ένα από τα πιο ισχυρά εργαλεία του οικοσυστήματος της Apple. Σου επιτρέπουν να μεταφέρεις επαναλαμβανόμενη λογική στο compile time, μειώνοντας τον boilerplate κώδικα — χωρίς να θυσιάζεις την ασφάλεια τύπων. Σε αυτόν τον αναλυτικό οδηγό για το 2026 θα δούμε λεπτομερώς πώς λειτουργούν τα freestanding και attached macros, πώς φτιάχνεις δικά σου από την αρχή, και κυρίως ποια είναι τα παραδείγματα που θα συναντήσεις πραγματικά σε production iOS εφαρμογές.
Swift Macros: Πλήρης Οδηγός για @freestanding και @attached με Παραδείγματα
Μάθε Swift Macros από το μηδέν: πώς λειτουργούν freestanding και attached macros, πώς δημιουργείς δικά σου, και πραγματικά παραδείγματα όπως @Observable, #Predicate και custom peer macros για Swift 6 και Xcode 16.

Σε αντίθεση με τα C preprocessor macros (που λειτουργούν με απλή αντικατάσταση συμβολοσειρών, κάτι αρκετά πρωτόγονο αν το σκεφτείς), τα Swift Macros είναι πραγματικός Swift κώδικας που εκτελείται κατά τη μεταγλώττιση. Δέχονται ως είσοδο ένα syntax tree μέσω της βιβλιοθήκης SwiftSyntax και παράγουν νέο Swift κώδικα, ο οποίος ελέγχεται πλήρως από τον compiler για ορθότητα τύπων.
Το πιο κρίσιμο χαρακτηριστικό; Λειτουργούν αθροιστικά. Μπορούν δηλαδή να προσθέσουν νέο κώδικα, αλλά ποτέ δεν διαγράφουν ή τροποποιούν τον υπάρχοντα. Αυτή η αρχή κάνει τη συμπεριφορά τους προβλέψιμη και τον παραγόμενο κώδικα εύκολο να εξεταστεί — κάτι που εγώ προσωπικά εκτιμώ πολύ μετά από χρόνια ταλαιπωρίας με runtime reflection σε άλλες γλώσσες.
Βασικά Πλεονεκτήματα
- Compile-time εκτέλεση: Καμία επιβάρυνση runtime performance
- Type-safe μετασχηματισμοί: Πλήρης έλεγχος τύπων στον παραγόμενο κώδικα
- Μείωση boilerplate: Αυτόματη παραγωγή επαναλαμβανόμενων μοτίβων
- Sandboxed εκτέλεση: Δεν έχουν πρόσβαση σε filesystem ή δίκτυο (καλό από άποψη ασφάλειας)
- Διαφάνεια: Μπορείς να δεις τον παραγόμενο κώδικα στο Xcode με δεξί κλικ → Expand Macro
Οι Δύο Κατηγορίες: Freestanding και Attached Macros
Τα Swift Macros χωρίζονται σε δύο μεγάλες κατηγορίες, με αρκετά διαφορετική σύνταξη και σκοπό:
- Freestanding macros — χρησιμοποιούν το πρόθεμα
#και στέκονται μόνα τους ως εκφράσεις ή δηλώσεις - Attached macros — χρησιμοποιούν το πρόθεμα
@και προσαρτώνται σε υπάρχουσες δηλώσεις (τύπους, μεταβλητές, συναρτήσεις)
Freestanding Macros σε Βάθος
Τα freestanding macros υπάρχουν σε δύο ρόλους: το @freestanding(expression) για macros που επιστρέφουν τιμή, και το @freestanding(declaration) για macros που παράγουν νέες δηλώσεις. Απλό μέχρι εδώ.
Παράδειγμα: Το #stringify Macro
Αυτό είναι το προεπιλεγμένο παράδειγμα που δημιουργεί το Xcode όταν φτιάχνεις νέο Swift Macro package — μάλλον το πρώτο που έχεις δει κι εσύ. Δέχεται μια έκφραση και επιστρέφει ένα tuple με την τιμή της και τον πηγαίο κώδικα ως string:
@freestanding(expression)
public macro stringify<T>(_ value: T) -> (T, String) =
#externalMacro(module: "MyMacrosImpl", type: "StringifyMacro")
// Χρήση
let (result, source) = #stringify(2 + 3)
print(result) // 5
print(source) // "2 + 3"
Παράδειγμα: Compile-Time URL Validation
Ένα από τα πιο πρακτικά freestanding macros είναι το #URL. Επικυρώνει τη συμβολοσειρά κατά τη μεταγλώττιση και προκαλεί compile error αν είναι μη έγκυρη — αντικαθιστώντας την επικίνδυνη force-unwrap πρακτική που, ομολογουμένως, όλοι μας έχουμε γράψει κάποια στιγμή:
// Χωρίς macro — δυνητικό crash στο runtime
let url = URL(string: "https://api.example.com/v1")!
// Με macro — compile-time validation
let safeUrl = #URL("https://api.example.com/v1")
// Αν γράψεις #URL("not a url") παίρνεις compile error
Built-in Freestanding Macros που Πρέπει να Γνωρίζεις
// #Predicate — δομημένα queries για SwiftData / Core Data
let adults = #Predicate<Person> { person in
person.age >= 18 && person.isActive
}
// #warning — προσαρμοσμένα compile-time warnings
#warning("Αυτή η API θα καταργηθεί στη v3.0 — μεταναστεύστε στο NewService")
// #file, #line, #function — source location macros (πάντα διαθέσιμα)
func logError(_ message: String, file: String = #file, line: Int = #line) {
print("[\(file):\(line)] \(message)")
}
Attached Macros σε Βάθος
Εδώ τα πράγματα γίνονται πιο ενδιαφέροντα. Τα attached macros προσαρτώνται σε υπάρχουσες δηλώσεις και μπορούν να παίξουν έναν ή περισσότερους από τους ακόλουθους ρόλους:
@attached(peer)— προσθέτει νέες δηλώσεις δίπλα στην προσαρτημένη@attached(accessor)— προσθέτει getter/setter accessors σε ιδιότητες@attached(memberAttribute)— προσθέτει attributes στα μέλη ενός τύπου@attached(member)— προσθέτει νέα μέλη μέσα σε τύπο ή extension@attached(extension)— προσθέτει extensions στον τύπο (αντικαθιστά το παλιό conformance)
Παράδειγμα: Το @Observable Macro
Πιθανότατα το έχεις ήδη χρησιμοποιήσει στη SwiftUI. Το @Observable αντικαθιστά το παλιό ObservableObject protocol με @Published properties, μειώνοντας δραστικά τον boilerplate. Δες τη διαφορά:
// Πριν τα macros
class UserViewModel: ObservableObject {
@Published var name: String = ""
@Published var email: String = ""
@Published var isLoading: Bool = false
}
// Με το @Observable macro
@Observable
class UserViewModel {
var name: String = ""
var email: String = ""
var isLoading: Bool = false
}
Στο SwiftUI view, χρησιμοποιείς απλά @State αντί για @StateObject ή @ObservedObject — πιο καθαρό, πιο intuitive:
struct ProfileView: View {
@State private var viewModel = UserViewModel()
var body: some View {
Form {
TextField("Όνομα", text: $viewModel.name)
TextField("Email", text: $viewModel.email)
}
}
}
Παράδειγμα: Custom Peer Macro για Async APIs
Να ένα peer macro που μετατρέπει αυτόματα completion-based functions σε async/await versions — τύπου χρυσός αν έχεις μεγάλο legacy codebase:
@attached(peer, names: overloaded)
public macro AddAsync() =
#externalMacro(module: "MyMacrosImpl", type: "AddAsyncMacro")
// Χρήση
@AddAsync
func fetchUser(id: String, completion: @escaping (User?) -> Void) {
URLSession.shared.dataTask(with: url) { data, _, _ in
completion(data.flatMap { try? JSONDecoder().decode(User.self, from: $0) })
}.resume()
}
// Το macro παράγει αυτόματα την async έκδοση
func fetchUser(id: String) async -> User? {
await withCheckedContinuation { continuation in
fetchUser(id: id) { user in
continuation.resume(returning: user)
}
}
}
Δημιουργία Custom Macro Βήμα προς Βήμα
Ώρα να βρωμίσουμε τα χέρια μας. Για να φτιάξεις δικό σου macro από το Xcode:
- Επίλεξε File → New → Package...
- Διάλεξε το template Swift Macro
- Δώσε όνομα στο package (π.χ.
MyMacros)
Δομή του Macro Package
Το αυτόματα παραγόμενο package περιέχει τα εξής αρχεία:
MyMacros.swift— δηλώσεις των macros (το public API)MyMacrosMacro.swift— η υλοποίηση χρησιμοποιώντας SwiftSyntaxMyMacrosClient/main.swift— εκτελέσιμο για δοκιμήTests/MyMacrosTests/MyMacrosTests.swift— unit tests
Υλοποίηση ενός @CaseDetection Macro
Ας φτιάξουμε ένα member macro που, για κάθε case ενός enum, παράγει μια computed property τύπου Bool — έλεγχος δηλαδή αν είναι η τρέχουσα τιμή. Πολύ συνηθισμένο pattern όταν δουλεύεις με state machines:
import SwiftSyntax
import SwiftSyntaxMacros
public struct CaseDetectionMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard let enumDecl = declaration.as(EnumDeclSyntax.self) else {
return []
}
return enumDecl.memberBlock.members
.compactMap { $0.decl.as(EnumCaseDeclSyntax.self) }
.flatMap { $0.elements }
.map { element in
let name = element.name.text
return DeclSyntax(stringLiteral: """
var is\(name.prefix(1).uppercased() + name.dropFirst()): Bool {
if case .\(name) = self { return true }
return false
}
""")
}
}
}
Η δήλωση του macro στο public module:
@attached(member, names: arbitrary)
public macro CaseDetection() =
#externalMacro(module: "MyMacrosImpl", type: "CaseDetectionMacro")
// Παράδειγμα χρήσης
@CaseDetection
enum NetworkState {
case idle
case loading
case loaded(data: Data)
case failed(error: Error)
}
// Παράγονται αυτόματα:
// var isIdle: Bool { ... }
// var isLoading: Bool { ... }
// var isLoaded: Bool { ... }
// var isFailed: Bool { ... }
Testing για Swift Macros
Η Apple παρέχει το framework SwiftSyntaxMacrosTestSupport για unit testing των macros, με τη συνάρτηση assertMacroExpansion. Και ναι, πρέπει οπωσδήποτε να γράφεις tests — τα macros χωρίς tests είναι δράκοι σε αναμονή:
import XCTest
import SwiftSyntaxMacrosTestSupport
@testable import MyMacrosImpl
final class CaseDetectionTests: XCTestCase {
func testCaseDetectionExpansion() {
assertMacroExpansion(
"""
@CaseDetection
enum Status {
case active
case inactive
}
""",
expandedSource: """
enum Status {
case active
case inactive
var isActive: Bool {
if case .active = self { return true }
return false
}
var isInactive: Bool {
if case .inactive = self { return true }
return false
}
}
""",
macros: ["CaseDetection": CaseDetectionMacro.self]
)
}
}
Βέλτιστες Πρακτικές για Swift Macros
1. Κράτα τα Macros Απλά
Όσο πιο πολύπλοκο γίνεται ένα macro, τόσο πιο δύσκολο είναι να γίνει debug. Αν χρειάζεσαι σύνθετη λογική, χώρισέ την σε πολλά μικρότερα macros. Πίστεψέ με σε αυτό — έχω χάσει ώρες προσπαθώντας να debug-άρω ένα macro που έκανε τρία πράγματα ταυτόχρονα.
2. Πάντα Παρέχε Καλά Compile-Time Errors
Χρησιμοποίησε το context.diagnose(...) για να δίνεις σαφή μηνύματα όταν το macro δεν μπορεί να εφαρμοστεί:
guard let enumDecl = declaration.as(EnumDeclSyntax.self) else {
let diagnostic = Diagnostic(
node: node,
message: MacroError.notAnEnum
)
context.diagnose(diagnostic)
return []
}
3. Απόφυγε την Υπερβολική Χρήση
Τα macros είναι ισχυρά, αλλά κάνουν τον κώδικα λιγότερο διαφανή. Χρησιμοποίησέ τα όταν αληθινά μειώνουν boilerplate, όχι για να κρύψεις λογική. Το "magic code" σπάνια ενθουσιάζει τον επόμενο που θα το διαβάσει.
4. Έλεγξε τον Παραγόμενο Κώδικα
Στο Xcode, κάνε δεξί κλικ σε ένα macro call και επίλεξε Expand Macro. Είναι το πιο σημαντικό εργαλείο για να καταλάβεις τι ακριβώς συμβαίνει — και ειλικρινά, η πρώτη φορά που το ανακάλυψα ήταν αποκάλυψη.
Συνηθισμένα Σφάλματα και Πώς να τα Αντιμετωπίσεις
"External macro implementation could not be found"
Αυτό συμβαίνει συνήθως όταν το όνομα του module στη δήλωση #externalMacro δεν ταιριάζει με το πραγματικό όνομα του compiler plugin target. Έλεγξε το Package.swift — εκεί κρύβεται σχεδόν πάντα το πρόβλημα.
"Macros are not enabled"
Στο Xcode 16+, την πρώτη φορά που χρησιμοποιείς ένα macro από ένα package, ο compiler ζητά άδεια (για λόγους ασφάλειας). Πάτησε Trust & Enable και είσαι έτοιμος.
Συχνές Ερωτήσεις (FAQ)
Ποια έκδοση του Swift απαιτείται για τα Swift Macros;
Τα Swift Macros απαιτούν Swift 5.9 ή νεότερο και Xcode 15+. Για την πλήρη γκάμα δυνατοτήτων (συμπεριλαμβανομένου του @attached(extension)), προτείνεται Swift 6.0+ με Xcode 16 ή μεταγενέστερο.
Επηρεάζουν τα macros την απόδοση runtime;
Όχι. Τα macros επεκτείνονται κατά τη μεταγλώττιση και ο παραγόμενος κώδικας μεταγλωττίζεται όπως οποιοσδήποτε άλλος Swift κώδικας. Δεν υπάρχει runtime cost. Ωστόσο, μπορεί να αυξήσουν αισθητά τον χρόνο compilation σε μεγάλα projects — κάτι που αξίζει να το ξέρεις.
Ποια είναι η διαφορά μεταξύ macros και code generation εργαλείων όπως το Sourcery;
Σε αντίθεση με τα code generation tools που τρέχουν ως ξεχωριστό βήμα build και παράγουν αρχεία στον δίσκο, τα Swift Macros εκτελούνται μέσα στον compiler και ο κώδικάς τους δεν αποθηκεύεται. Επίσης, είναι πλήρως type-aware και ενσωματωμένα στο IDE.
Μπορώ να χρησιμοποιήσω macros σε iOS app development χωρίς να φτιάξω custom;
Φυσικά. Τα built-in macros της Apple όπως @Observable, @Model (SwiftData), #Predicate, και #Preview καλύπτουν τις περισσότερες ανάγκες ενός σύγχρονου iOS developer — χωρίς να χρειαστεί να γράψεις δικά σου.
Πώς κάνω debug ένα macro που δεν παράγει τον κώδικα που περιμένω;
Η πιο αποτελεσματική προσέγγιση είναι ο συνδυασμός τριών τεχνικών: (1) χρησιμοποίησε το Expand Macro στο Xcode για να δεις τον παραγόμενο κώδικα, (2) γράψε unit tests με assertMacroExpansion για να επαληθεύσεις το expansion, και (3) πρόσθεσε print(syntax.description) μέσα στη συνάρτηση expansion για να δεις το input syntax tree.
Συμπέρασμα
Τα Swift Macros αποτελούν θεμελιώδες εργαλείο για κάθε σύγχρονο iOS developer το 2026. Είτε χρησιμοποιείς τα built-in macros της Apple όπως το @Observable και το @Model, είτε φτιάχνεις δικά σου για να αυτοματοποιήσεις επαναλαμβανόμενα μοτίβα στο codebase σου, η κατανόηση του πώς λειτουργούν θα κάνει τον κώδικά σου πιο καθαρό, ασφαλέστερο και πιο εκφραστικό.
Ξεκίνα με τα built-in, εξοικειώσου με τη χρήση τους, και όταν εντοπίσεις boilerplate που επαναλαμβάνεται σε όλο το project, σκέψου αν ένα custom macro θα μπορούσε να το εξαλείψει. Αυτή είναι η σωστή προσέγγιση για να εκμεταλλευτείς στο έπακρο τη δύναμη του Swift compile-time metaprogramming.


