Swift 5.9 ile tanıtılan Macros, Swift'in en güçlü ve en az anlaşılan özelliklerinden biridir. Boilerplate kodu elimine etmek, compile-time güvenliği artırmak ve kod üretimini otomatikleştirmek için devrim niteliğinde bir araçtır. Bu rehberde macro sistemini A'dan Z'ye öğreneceksiniz.
İçindekiler
- Macro Türleri
- Custom Macro Oluşturma
- @Codable Enhancement Macro
- #URL Macro - Compile-Time Validation
- Testing Macros
- Macro Error Handling
- Macro Testing
- Best Practices
- Macro Türleri Karşılaştırması
- Sonuç
Macro Türleri
Swift'te iki ana macro türü vardır:
1. Freestanding Macros
Bağımsız olarak çağrılabilen macro'lar:
swift
1// #stringify - Expression'ı string'e çevirir2let (value, code) = #stringify(2 + 3)3// value = 5, code = "2 + 3"4 5// #warning ve #error - Compile-time mesajlar6#warning("Bu kod refactor edilmeli")7#error("Bu platform desteklenmiyor")8 9// Custom URL macro10let url = #URL("https://api.example.com/users")11// Compile-time URL validation!2. Attached Macros
Bir declaration'a bağlanan macro'lar:
swift
1// @Observable - SwiftUI için state management2@Observable3class UserViewModel {4 var name: String = ""5 var email: String = ""6}7// Otomatik olarak ObservableObject uyumluluğu ekler8 9// @Model - SwiftData için persistence10@Model11class Product {12 var name: String13 var price: Decimal14 var stock: Int15}Custom Macro Oluşturma
Proje Yapısı
swift
1MyMacros/2├── Package.swift3├── Sources/4│ ├── MyMacros/ # Macro declarations5│ │ └── MyMacros.swift6│ ├── MyMacrosImpl/ # Macro implementations7│ │ └── MyMacrosImpl.swift8│ └── MyMacrosClient/ # Test client9│ └── main.swift10└── Tests/11 └── MyMacrosTests/Package.swift
swift
1// swift-tools-version: 5.92import PackageDescription3import CompilerPluginSupport4 5let package = Package(6 name: "MyMacros",7 platforms: [.macOS(.v10_15), .iOS(.v13)],8 products: [9 .library(name: "MyMacros", targets: ["MyMacros"]),10 ],11 dependencies: [12 .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"),13 ],14 targets: [15 // Macro implementation16 .macro(17 name: "MyMacrosImpl",18 dependencies: [19 .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),20 .product(name: "SwiftCompilerPlugin", package: "swift-syntax"),21 ]22 ),23 // Library that exposes macros24 .target(25 name: "MyMacros",26 dependencies: ["MyMacrosImpl"]27 ),28 // Tests29 .testTarget(30 name: "MyMacrosTests",31 dependencies: [32 "MyMacrosImpl",33 .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),34 ]35 ),36 ]37)@AutoInit Macro
Otomatik initializer oluşturan bir macro yazalım:
swift
1// MyMacros.swift - Macro declaration2@attached(member, names: named(init))3public macro AutoInit() = #externalMacro(module: "MyMacrosImpl", type: "AutoInitMacro")4 5// MyMacrosImpl.swift - Macro implementation6import SwiftSyntax7import SwiftSyntaxMacros8import SwiftCompilerPlugin9 10public struct AutoInitMacro: MemberMacro {11 public static func expansion(12 of node: AttributeSyntax,13 providingMembersOf declaration: some DeclGroupSyntax,14 in context: some MacroExpansionContext15 ) throws -> [DeclSyntax] {16 17 // Get stored properties18 let properties = declaration.memberBlock.members19 .compactMap { $0.decl.as(VariableDeclSyntax.self) }20 .filter { $0.bindingSpecifier.tokenKind == .keyword(.var) }21 .flatMap { $0.bindings }22 .compactMap { binding -> (String, String)? in23 guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self),24 let type = binding.typeAnnotation?.type else {25 return nil26 }27 return (pattern.identifier.text, type.description.trimmingCharacters(in: .whitespaces))28 }29 30 // Generate initializer31 let parameters = properties32 .map { "\($0.0): \($0.1)" }33 .joined(separator: ", ")34 35 let assignments = properties36 .map { "self.\($0.0) = \($0.0)" }37 .joined(separator: "\n ")38 39 let initDecl: DeclSyntax = """40 init(\(raw: parameters)) {41 \(raw: assignments)42 }43 """44 45 return [initDecl]46 }47}48 49@main50struct MyMacrosPlugin: CompilerPlugin {51 let providingMacros: [Macro.Type] = [52 AutoInitMacro.self,53 ]54}Kullanım
swift
1import MyMacros2 3@AutoInit4struct User {5 var id: UUID6 var name: String7 var email: String8 var age: Int9}10 11// Generates:12// init(id: UUID, name: String, email: String, age: Int) {13// self.id = id14// self.name = name15// self.email = email16// self.age = age17// }18 19let user = User(id: UUID(), name: "John", email: "[email protected]", age: 30)@Codable Enhancement Macro
swift
1@attached(member, names: named(CodingKeys), named(init(from:)), named(encode(to:)))2@attached(extension, conformances: Codable)3public macro EnhancedCodable() = #externalMacro(module: "MyMacrosImpl", type: "EnhancedCodableMacro")4 5// Usage6@EnhancedCodable7struct Product {8 @CodableKey("product_name")9 var name: String10 11 @CodableKey("unit_price")12 var price: Decimal13 14 @CodableIgnore15 var localCache: Data?16}#URL Macro - Compile-Time Validation
swift
1@freestanding(expression)2public macro URL(_ string: String) -> URL = #externalMacro(module: "MyMacrosImpl", type: "URLMacro")3 4public struct URLMacro: ExpressionMacro {5 public static func expansion(6 of node: some FreestandingMacroExpansionSyntax,7 in context: some MacroExpansionContext8 ) throws -> ExprSyntax {9 guard let argument = node.argumentList.first?.expression,10 let stringLiteral = argument.as(StringLiteralExprSyntax.self),11 let urlString = stringLiteral.segments.first?.as(StringSegmentSyntax.self)?.content.text else {12 throw MacroError.invalidArgument13 }14 15 // Compile-time validation16 guard URL(string: urlString) != nil else {17 throw MacroError.invalidURL(urlString)18 }19 20 return "URL(string: \(argument))!"21 }22}23 24// Usage25let apiURL = #URL("https://api.example.com") // ✅ Compiles26let badURL = #URL("not a url") // ❌ Compile error!Testing Macros
swift
1import SwiftSyntaxMacrosTestSupport2import XCTest3 4final class AutoInitMacroTests: XCTestCase {5 func testAutoInit() {6 assertMacroExpansion(7 """8 @AutoInit9 struct User {10 var name: String11 var age: Int12 }13 """,14 expandedSource: """15 struct User {16 var name: String17 var age: Int18 init(name: String, age: Int) {19 self.name = name20 self.age = age21 }22 }23 """,24 macros: ["AutoInit": AutoInitMacro.self]25 )26 }27}Macro Error Handling
swift
1// MARK: - Custom Macro Errors2enum MacroError: Error, DiagnosticMessage {3 case invalidApplication(String)4 case missingArgument(String)5 case invalidURL(String)6 7 var message: String {8 switch self {9 case .invalidApplication(let msg): return msg10 case .missingArgument(let arg): return "Missing argument: \(arg)"11 case .invalidURL(let url): return "Invalid URL: \(url)"12 }13 }14 15 var diagnosticID: MessageID {16 MessageID(domain: "MyMacros", id: String(describing: self))17 }18 19 var severity: DiagnosticSeverity { .error }20}Macro Testing
swift
1import SwiftSyntaxMacrosTestSupport2import XCTest3 4final class MyMacrosTests: XCTestCase {5 let testMacros: [String: Macro.Type] = [6 "AutoInit": AutoInitMacro.self,7 "URL": URLMacro.self,8 ]9 10 func testAutoInit_SimpleStruct() throws {11 assertMacroExpansion(12 """13 @AutoInit14 struct User {15 var name: String16 var age: Int17 }18 """,19 expandedSource: """20 struct User {21 var name: String22 var age: Int23 24 internal init(name: String, age: Int) {25 self.name = name26 self.age = age27 }28 }29 """,30 macros: testMacros31 )32 }33}Best Practices
- Macro'ları basit tutun: Tek bir işi iyi yapın
- Hata mesajları anlaşılır olsun: Kullanıcıya ne yanlış olduğunu açıklayın
- Test yazın: Her macro için kapsamlı testler
- Documentation: Macro'nun ne ürettiğini belgeleyin
- Performans: Compile-time'ı uzatmamaya dikkat edin
Macro Türleri Karşılaştırması
Macro Rolü | Anahtar Kelime | Açıklama | Kullanım Alanı |
|---|---|---|---|
Expression | @freestanding(expression) | Değer döndürür | #URL, #stringify |
Declaration | @freestanding(declaration) | Yeni declaration oluşturur | #warning, #error |
Peer | @attached(peer) | Yanına declaration ekler | Async wrapper |
Member | @attached(member) | Tipe member ekler | @AutoInit |
Accessor | @attached(accessor) | get/set/didSet ekler | @Published benzeri |
MemberAttribute | @attached(memberAttribute) | Member'lara attribute ekler | @CodableKey |
Extension | @attached(extension) | Extension + conformance ekler | @Observable |
💡 Altın İpucu: Macro geliştirirken assertMacroExpansion test fonksiyonunu her zaman kullanın. Macro'nuzun ürettiği kodu string olarak doğrulamak, beklenmeyen kod üretimini compile-time yerine test aşamasında yakalamanızı sağlar. Ayrıca Xcode'da herhangi bir macro kullanımına sağ tıklayıp "Expand Macro" seçeneği ile üretilen kodu anında görebilirsiniz.Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var:
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Sonuç
Swift Macros, boilerplate kodu elimine etmek ve compile-time güvenliği artırmak için güçlü bir araçtır. Custom macro'lar yazarak kod tabanınızı daha temiz ve güvenli hale getirebilirsiniz.
Kaynaklar
- Apple Developer - Swift Macros
- Swift.org - Macros Documentation
- WWDC23 - Write Swift Macros
- GitHub - swift-syntax
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?

