# Swift Macros: ตัวอย่างเชิงปฏิบัติของ metaprogramming > คู่มือฉบับสมบูรณ์ของ Swift Macros: การสร้างมาโคร freestanding และ attached, การจัดการ AST ด้วย swift-syntax และตัวอย่างเชิงปฏิบัติเพื่อกำจัดโค้ดซ้ำซาก - Published: 2026-03-11 - Updated: 2026-05-01 - Author: SharpSkill - Tags: swift, ios, macros, metaprogramming, swift-syntax - Reading time: 16 min --- Swift Macros ที่เปิดตัวพร้อมกับ Swift 5.9 และ Xcode 15 ถือเป็นการปฏิวัติวิธีเขียนโค้ด Swift โดยอนุญาตให้สร้างโค้ดในขั้นตอนคอมไพล์ ทำให้ลด boilerplate ลงโดยไม่สูญเสียความปลอดภัยของชนิดข้อมูลแบบสแตติก ต่างจากมาโครของพรีโพรเซสเซอร์ใน C ตรงที่ Swift Macros ปลอดภัยทางชนิด ผูกกับคอมไพเลอร์ และได้รับการสนับสนุนเต็มรูปแบบจากเครื่องมือพัฒนา > **ขอบเขตของคู่มือฉบับนี้** > > คู่มือฉบับนี้พาสำรวจการสร้าง Swift Macros ตั้งแต่ต้นจนจบ ตั้งแต่แนวคิดพื้นฐานไปจนถึงการนำไปใช้งานขั้นสูง พร้อมตัวอย่างโค้ดที่ใช้งานได้จริงและพร้อมปรับใช้กับโปรเจกต์ iOS ใด ๆ ## ทำความเข้าใจประเภทของ Swift Macros Swift มีมาโครหลักสองประเภท แต่ละประเภทรองรับสถานการณ์การใช้งานที่ต่างกัน มาโครแบบ freestanding ทำงานเป็นนิพจน์หรือดีคลาเรชันโดยอิสระ ในขณะที่มาโคร attached จะแนบกับดีคลาเรชันเดิมเพื่อปรับเปลี่ยนหรือเสริมความสามารถ ### มาโคร freestanding: นิพจน์และดีคลาเรชัน มาโคร freestanding เริ่มต้นด้วยสัญลักษณ์ `#` และสามารถส่งคืนค่า (นิพจน์) หรือสร้างดีคลาเรชันใหม่ได้ ต่อไปนี้คือตัวอย่างเชิงรูปธรรมของมาโครนิพจน์: ```swift // MacroUsage.swift // Freestanding expression macro - generates a value let buildInfo = #buildDate // Expansion → "2026-03-11 10:30:45" // Freestanding macro with arguments let message = #stringify(1 + 2) // Expansion → "1 + 2 = 3" // Freestanding declaration macro - creates declarations #makeCase("success", "failure", "pending") // Expansion → // case success // case failure // case pending ``` ความแตกต่างหลักระหว่างนิพจน์กับดีคลาเรชันอยู่ที่ผลลัพธ์: นิพจน์ให้ค่ากลับมา ส่วนดีคลาเรชันสร้างโค้ดเชิงโครงสร้าง (ชนิดข้อมูล ฟังก์ชัน ตัวแปร) ### มาโคร attached: ห้าบทบาท มาโคร attached เริ่มต้นด้วย `@` และวางอยู่หน้าดีคลาเรชัน Swift กำหนดบทบาทห้าแบบสำหรับมาโครเหล่านี้: ```swift // AttachedMacroRoles.swift // @attached(peer) - adds declarations at the same level @AddAsync func fetchUser(id: Int) -> User { ... } // Expansion → adds func fetchUserAsync(id: Int) async -> User // @attached(accessor) - adds getters/setters @UserDefault("theme") var currentTheme: String // Expansion → adds get { UserDefaults.standard.string(...) } // @attached(member) - adds members to a type @AutoEquatable struct Point { var x: Int var y: Int } // Expansion → adds static func == (lhs: Point, rhs: Point) -> Bool // @attached(memberAttribute) - applies attributes to members @CodableKeys struct Config { var apiUrl: String var timeout: Int } // Expansion → adds @CodingKey("api_url") before apiUrl // @attached(conformance) / @attached(extension) - adds conformances @Hashable struct User { var id: Int var name: String } // Expansion → adds extension User: Hashable { ... } ``` บทบาทเหล่านี้สามารถผสานรวมกันเพื่อสร้างมาโครที่ทรงพลังและสามารถแปลงโค้ดได้หลายมิติพร้อมกัน ## การตั้งค่าโปรเจกต์เพื่อสร้างมาโคร การสร้าง Swift Macros ต้องใช้ Swift Package ที่มีโครงสร้างเฉพาะ แพ็กเกจนี้ขึ้นอยู่กับ swift-syntax ซึ่งเป็นไลบรารีทางการสำหรับจัดการโค้ด Swift ในรูปแบบของ abstract syntax tree (AST) ### โครงสร้างของ Package.swift ```swift // Package.swift // swift-tools-version: 5.9 import PackageDescription let package = Package( name: "MyMacros", platforms: [.macOS(.v10_15), .iOS(.v13)], products: [ // Library exposing macros to the main project .library( name: "MyMacros", targets: ["MyMacros"] ), // Executable for testing macros .executable( name: "MyMacrosClient", targets: ["MyMacrosClient"] ) ], dependencies: [ // Required dependency for macros .package( url: "https://github.com/apple/swift-syntax.git", from: "509.0.0" ) ], targets: [ // Compiler plugin containing implementation .macro( name: "MyMacrosPlugin", dependencies: [ .product(name: "SwiftSyntax", package: "swift-syntax"), .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), .product(name: "SwiftCompilerPlugin", package: "swift-syntax") ] ), // Target exposing macro declarations .target( name: "MyMacros", dependencies: ["MyMacrosPlugin"] ), // Test client .executableTarget( name: "MyMacrosClient", dependencies: ["MyMacros"] ), // Unit tests .testTarget( name: "MyMacrosTests", dependencies: [ "MyMacrosPlugin", .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax") ] ) ] ) ``` การตั้งค่านี้แยกอย่างชัดเจนระหว่างการประกาศมาโคร (สิ่งที่โค้ดฝั่งไคลเอนต์เห็น) กับการนำไปใช้ (ที่ทำงานช่วงคอมไพล์) > **การจัดระเบียบที่แนะนำ** > > ต้องมีไฟล์อย่างน้อยสามไฟล์: MyMacros.swift สำหรับการประกาศ MyMacrosPlugin.swift สำหรับการนำไปใช้ และ MyMacrosTests.swift สำหรับการทดสอบ การแยกแบบนี้ช่วยให้บำรุงรักษาง่ายขึ้น ## การสร้างมาโครนิพจน์ มาโครนิพจน์สร้างค่าที่นำไปใช้ได้ทันทีในโค้ด ต่อไปนี้คือวิธีสร้างมาโคร `#unwrap` ที่ใช้แกะค่า optional พร้อมข้อความผิดพลาดที่กำหนดเองและรวมชื่อตัวแปรไว้ด้วย ### การประกาศมาโคร ```swift // MyMacros.swift import Foundation /// Macro that unwraps an optional with an explicit error message /// Usage: let value = #unwrap(optionalValue) /// Expansion: guard let optionalValue else { fatalError("...") }; optionalValue @freestanding(expression) public macro unwrap(_ value: T?) -> T = #externalMacro( module: "MyMacrosPlugin", type: "UnwrapMacro" ) ``` ลายเซ็นระบุว่ามาโครรับค่า optional และส่งคืนค่าที่ไม่ใช่ optional `#externalMacro` ชี้ไปยังการนำไปใช้ในปลั๊กอิน ### การนำไปใช้ด้วย swift-syntax ```swift // UnwrapMacro.swift import SwiftSyntax import SwiftSyntaxMacros import SwiftCompilerPlugin public struct UnwrapMacro: ExpressionMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> ExprSyntax { // Get the first argument passed to the macro guard let argument = node.argumentList.first?.expression else { throw MacroError.missingArgument } // Extract the variable name for the error message let variableName = argument.description.trimmingCharacters( in: .whitespacesAndNewlines ) // Generate the expansion code // Uses an immediately-invoked closure to encapsulate the guard return """ { guard let value = \(argument) else { fatalError("Failed to unwrap '\\(\(literal: variableName))' - value was nil") } return value }() """ } } // Custom errors for macros enum MacroError: Error, CustomStringConvertible { case missingArgument case invalidSyntax(String) var description: String { switch self { case .missingArgument: return "The macro requires an argument" case .invalidSyntax(let message): return "Invalid syntax: \(message)" } } } ``` เมท็อด `expansion` รับโหนด AST ที่แทนการเรียกใช้มาโคร พร้อมบริบทของการคอมไพล์ และส่งคืน `ExprSyntax` ซึ่งบรรจุโค้ดที่สร้างขึ้น ### การลงทะเบียนปลั๊กอิน ```swift // MyMacrosPlugin.swift import SwiftCompilerPlugin import SwiftSyntaxMacros @main struct MyMacrosPlugin: CompilerPlugin { // List all macros provided by this plugin let providingMacros: [Macro.Type] = [ UnwrapMacro.self, // Add other macros here ] } ``` จุดเริ่มต้นนี้บอกคอมไพเลอร์เกี่ยวกับมาโครที่ปลั๊กอินให้บริการ ## การสร้างมาโคร attached แบบ member มาโคร member เพิ่มสมาชิก (พร็อพเพอร์ตี้, เมท็อด, ชนิดซ้อน) ให้กับชนิดที่มีอยู่แล้ว ต่อไปนี้คือมาโคร `@AutoInit` ที่สร้าง initializer สำหรับ stored property ทั้งหมดโดยอัตโนมัติ ### การประกาศและการนำไปใช้แบบเต็ม ```swift // MyMacros.swift /// Automatically generates an initializer with all stored properties @attached(member, names: named(init)) public macro AutoInit() = #externalMacro( module: "MyMacrosPlugin", type: "AutoInitMacro" ) ``` ```swift // AutoInitMacro.swift import SwiftSyntax import SwiftSyntaxMacros public struct AutoInitMacro: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { // Verify the macro is applied to a struct or class guard declaration.is(StructDeclSyntax.self) || declaration.is(ClassDeclSyntax.self) else { throw MacroError.invalidSyntax( "@AutoInit can only be applied to structs and classes" ) } // Collect stored properties let properties = declaration.memberBlock.members .compactMap { $0.decl.as(VariableDeclSyntax.self) } .filter { isStoredProperty($0) } // Generate initializer parameters let parameters = properties.compactMap { property -> String? in guard let binding = property.bindings.first, let identifier = binding.pattern.as(IdentifierPatternSyntax.self), let type = binding.typeAnnotation?.type else { return nil } let name = identifier.identifier.text let typeName = type.description.trimmingCharacters(in: .whitespaces) // Check if the property has a default value if binding.initializer != nil { return "\(name): \(typeName) = \(binding.initializer!.value)" } return "\(name): \(typeName)" } // Generate assignments in the init body let assignments = properties.compactMap { property -> String? in guard let binding = property.bindings.first, let identifier = binding.pattern.as(IdentifierPatternSyntax.self) else { return nil } let name = identifier.identifier.text return "self.\(name) = \(name)" } // Build the complete initializer let initDecl: DeclSyntax = """ public init(\(raw: parameters.joined(separator: ", "))) { \(raw: assignments.joined(separator: "\n ")) } """ return [initDecl] } // Check if a variable is a stored property (not computed) private static func isStoredProperty(_ variable: VariableDeclSyntax) -> Bool { guard let binding = variable.bindings.first else { return false } // A computed property has an accessor block with get/set if let accessor = binding.accessorBlock { // If it's a block with explicit accessors, it's computed if accessor.accessors.is(AccessorDeclListSyntax.self) { return false } } // let or var without accessor = stored property return true } } ``` ### การใช้งานมาโคร AutoInit ```swift // UserModel.swift @AutoInit struct User { let id: UUID var name: String var email: String var isActive: Bool = true } // Automatically generated code: // public init(id: UUID, name: String, email: String, isActive: Bool = true) { // self.id = id // self.name = name // self.email = email // self.isActive = isActive // } // Usage let user = User(id: UUID(), name: "Alice", email: "alice@example.com") // isActive uses the default value ``` มาโครนี้ลด boilerplate ของ initializer ซึ่งมีประโยชน์อย่างยิ่งสำหรับโมเดลข้อมูลที่มีพร็อพเพอร์ตี้จำนวนมาก ## มาโคร attached peer สำหรับสร้างเวอร์ชัน async มาโคร peer เพิ่มดีคลาเรชันในระดับเดียวกับดีคลาเรชันที่ถูกตั้งโน้ต ต่อไปนี้คือมาโคร `@AddAsync` ที่สร้างเวอร์ชัน async ของฟังก์ชันที่อิงกับ completion handler ```swift // MyMacros.swift /// Automatically generates an async version of a function with completion handler @attached(peer, names: suffixed(Async)) public macro AddAsync() = #externalMacro( module: "MyMacrosPlugin", type: "AddAsyncMacro" ) ``` ```swift // AddAsyncMacro.swift import SwiftSyntax import SwiftSyntaxMacros public struct AddAsyncMacro: PeerMacro { public static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { // Verify it's a function guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else { throw MacroError.invalidSyntax( "@AddAsync requires a function" ) } let functionName = funcDecl.name.text let asyncFunctionName = "\(functionName)Async" // Analyze parameters to find the completion handler let parameters = funcDecl.signature.parameterClause.parameters // Filter parameters (exclude completion handler) var regularParams: [String] = [] var completionType: String? = nil for param in parameters { let paramType = param.type.description // Detect a completion handler (closure with Result or simple value) if paramType.contains("->") && paramType.contains("Void") { // Extract the return type from completion completionType = extractCompletionReturnType(from: paramType) } else { let paramName = param.firstName.text let paramSecondName = param.secondName?.text let label = paramSecondName ?? paramName regularParams.append("\(paramName): \(paramType)") } } guard let returnType = completionType else { throw MacroError.invalidSyntax( "No completion handler found" ) } // Generate arguments for internal call let callArgs = parameters.dropLast().map { param in let name = param.firstName.text return "\(name): \(name)" }.joined(separator: ", ") // Generate the async function let asyncFunc: DeclSyntax = """ func \(raw: asyncFunctionName)(\(raw: regularParams.joined(separator: ", "))) async throws -> \(raw: returnType) { try await withCheckedThrowingContinuation { continuation in \(raw: functionName)(\(raw: callArgs.isEmpty ? "" : callArgs + ", ")completion: { result in switch result { case .success(let value): continuation.resume(returning: value) case .failure(let error): continuation.resume(throwing: error) } }) } } """ return [asyncFunc] } // Extract return type from a Result type private static func extractCompletionReturnType(from type: String) -> String { // Simplified pattern - in production, use the AST if let match = type.range(of: #"Result<([^,]+)"#, options: .regularExpression) { var result = String(type[match]) result = result.replacingOccurrences(of: "Result<", with: "") return result.trimmingCharacters(in: .whitespaces) } return "Void" } } ``` ### สาธิตการใช้มาโคร AddAsync ```swift // NetworkService.swift class NetworkService { @AddAsync func fetchUser( id: Int, completion: @escaping (Result) -> Void ) { // Implementation with callback URLSession.shared.dataTask(with: URL(string: "/users/\(id)")!) { data, _, error in if let error = error { completion(.failure(error)) } else if let data = data { let user = try? JSONDecoder().decode(User.self, from: data) completion(.success(user!)) } }.resume() } // Automatically generates: // func fetchUserAsync(id: Int) async throws -> User { // try await withCheckedThrowingContinuation { continuation in // fetchUser(id: id, completion: { result in // switch result { // case .success(let value): // continuation.resume(returning: value) // case .failure(let error): // continuation.resume(throwing: error) // } // }) // } // } } // Modern usage with async/await let user = try await networkService.fetchUserAsync(id: 42) ``` > **ข้อจำกัดของมาโคร peer** > > ชื่อฟังก์ชันที่สร้างขึ้นต้องประกาศใน `names:` ของแอตทริบิวต์ `@attached` ในที่นี้ `suffixed(Async)` หมายความว่าฟังก์ชันที่สร้างใหม่จะนำคำต่อท้าย "Async" ต่อท้ายชื่อเดิม ## การทดสอบยูนิตสำหรับมาโคร การทดสอบมาโครเป็นเรื่องสำคัญ เพราะมาโครสร้างโค้ดที่จะถูกคอมไพล์ในภายหลัง Swift มี `SwiftSyntaxMacrosTestSupport` เพื่อช่วยให้การทดสอบลักษณะนี้ง่ายขึ้น ```swift // MyMacrosTests.swift import SwiftSyntaxMacros import SwiftSyntaxMacrosTestSupport import XCTest @testable import MyMacrosPlugin final class MyMacrosTests: XCTestCase { // Dictionary of macros to test let testMacros: [String: Macro.Type] = [ "unwrap": UnwrapMacro.self, "AutoInit": AutoInitMacro.self, "AddAsync": AddAsyncMacro.self ] func testUnwrapMacroExpansion() throws { assertMacroExpansion( """ let value = #unwrap(optionalString) """, expandedSource: """ let value = { guard let value = optionalString else { fatalError("Failed to unwrap 'optionalString' - value was nil") } return value }() """, macros: testMacros ) } func testAutoInitMacroWithStruct() throws { assertMacroExpansion( """ @AutoInit struct Point { let x: Int var y: Int } """, expandedSource: """ struct Point { let x: Int var y: Int public init(x: Int, y: Int) { self.x = x self.y = y } } """, macros: testMacros ) } func testAutoInitWithDefaultValues() throws { assertMacroExpansion( """ @AutoInit struct Config { var timeout: Int = 30 var retryCount: Int } """, expandedSource: """ struct Config { var timeout: Int = 30 var retryCount: Int public init(timeout: Int = 30, retryCount: Int) { self.timeout = timeout self.retryCount = retryCount } } """, macros: testMacros ) } func testAutoInitFailsOnEnum() throws { assertMacroExpansion( """ @AutoInit enum Status { case active } """, expandedSource: """ enum Status { case active } """, diagnostics: [ DiagnosticSpec( message: "@AutoInit can only be applied to structs and classes", line: 1, column: 1 ) ], macros: testMacros ) } } ``` การทดสอบยืนยันว่ามาโครขยายโค้ดอย่างถูกต้องและให้ข้อความผิดพลาดที่เหมาะสมเมื่อใช้งานไม่ถูกต้อง ## มาโครขั้นสูง: property wrapper แบบ observable มาโครนี้ผสมหลายบทบาทเข้าด้วยกันเพื่อสร้างระบบติดตามพร็อพเพอร์ตี้พร้อมการแจ้งเตือนอัตโนมัติ ```swift // MyMacros.swift /// Adds automatic property change observation @attached(accessor) @attached(peer, names: prefixed(_)) public macro Observable() = #externalMacro( module: "MyMacrosPlugin", type: "ObservableMacro" ) ``` ```swift // ObservableMacro.swift import SwiftSyntax import SwiftSyntaxMacros // Implements both roles: accessor and peer public enum ObservableMacro {} extension ObservableMacro: AccessorMacro { public static func expansion( of node: AttributeSyntax, providingAccessorsOf declaration: some DeclSyntax, in context: some MacroExpansionContext ) throws -> [AccessorDeclSyntax] { guard let varDecl = declaration.as(VariableDeclSyntax.self), let binding = varDecl.bindings.first, let identifier = binding.pattern.as(IdentifierPatternSyntax.self) else { return [] } let name = identifier.identifier.text let storageName = "_\(name)" // Generate get and set accessors let getter: AccessorDeclSyntax = """ get { access(keyPath: \\.\(raw: name)) return \(raw: storageName) } """ let setter: AccessorDeclSyntax = """ set { withMutation(keyPath: \\.\(raw: name)) { \(raw: storageName) = newValue } } """ return [getter, setter] } } extension ObservableMacro: PeerMacro { public static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard let varDecl = declaration.as(VariableDeclSyntax.self), let binding = varDecl.bindings.first, let identifier = binding.pattern.as(IdentifierPatternSyntax.self), let type = binding.typeAnnotation?.type else { return [] } let name = identifier.identifier.text let storageName = "_\(name)" let typeName = type.description // Generate private storage property let initializer = binding.initializer.map { " \($0)" } ?? "" let storageDecl: DeclSyntax = """ private var \(raw: storageName): \(raw: typeName)\(raw: initializer) """ return [storageDecl] } } ``` ### การใช้รูปแบบ Observable ```swift // ViewModel.swift @Observable class UserViewModel { @Observable var name: String = "" @Observable var age: Int = 0 @Observable var isActive: Bool = true // Generated code for each property: // private var _name: String = "" // var name: String { // get { // access(keyPath: \.name) // return _name // } // set { // withMutation(keyPath: \.name) { // _name = newValue // } // } // } } ``` Apple ใช้รูปแบบเดียวกันนี้ในเฟรมเวิร์ก Observation ใหม่ตั้งแต่ Swift 5.9 ## การดีบักและตรวจสอบมาโคร Xcode มีเครื่องมือหลายอย่างที่ช่วยดีบักมาโครและทำความเข้าใจโค้ดที่สร้างขึ้น ### การขยายใน Xcode ```swift // DebuggingMacros.swift // Right-click on macro call → "Expand Macro" // Displays generated code inline @AutoInit struct Product { let id: UUID var name: String var price: Decimal } // To see the expansion: // 1. Right-click on @AutoInit // 2. Select "Expand Macro" // 3. Generated code displays inline for inspection and debugging ``` ### การบันทึกล็อกระหว่างการพัฒนา ```swift // DebugMacro.swift public struct DebugMacro: ExpressionMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> ExprSyntax { // Print the node's AST to understand the structure print("=== DEBUG MACRO ===") print("Node: \(node)") print("Arguments: \(node.argumentList)") // Complete dump of the syntax tree dump(node) // Continue with normal expansion return "42" } } ``` ### สำรวจ AST ด้วย swift-ast-explorer เครื่องมือออนไลน์ [swift-ast-explorer.com](https://swift-ast-explorer.com) ใช้แสดงต้นไม้ไวยากรณ์ของโค้ด Swift ใด ๆ ก็ได้ และเป็นเครื่องมือสำคัญที่ช่วยให้เข้าใจวิธีเดินทางผ่านโหนด AST เมื่อต้องสร้างมาโคร ## แนวทางปฏิบัติที่ดีสำหรับ Swift Macros การสร้างมาโครที่ดูแลรักษาง่ายต้องอาศัยการปฏิบัติตามแนวทางบางประการและการหลีกเลี่ยงข้อผิดพลาดที่พบบ่อย ### การตรวจสอบและข้อความผิดพลาด ```swift // ValidationBestPractices.swift public struct ValidatedMacro: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { // ✅ Validate usage context guard declaration.is(StructDeclSyntax.self) else { // ✅ Clear error messages with possible localization context.diagnose( Diagnostic( node: node, message: MacroDiagnosticMessage( id: "invalid-target", message: "This macro can only be applied to structs", severity: .error ) ) ) return [] } // ✅ Check required arguments guard let arguments = node.arguments else { context.diagnose( Diagnostic( node: node, message: MacroDiagnosticMessage( id: "missing-args", message: "Required arguments missing", severity: .error ) ) ) return [] } // Implementation... return [] } } // Structure for diagnostic messages struct MacroDiagnosticMessage: DiagnosticMessage { let id: String let message: String let severity: DiagnosticSeverity var diagnosticID: MessageID { MessageID(domain: "MyMacros", id: id) } } ``` ### การสร้างโค้ดที่อ่านง่าย ```swift // ReadableCodeGeneration.swift // ❌ Hard-to-read generated code let badCode: DeclSyntax = "public init(a:Int,b:String,c:Bool){self.a=a;self.b=b;self.c=c}" // ✅ Properly formatted generated code let goodCode: DeclSyntax = """ public init( a: Int, b: String, c: Bool ) { self.a = a self.b = b self.c = c } """ ``` โค้ดที่สร้างขึ้นควรอ่านง่ายเทียบเท่าโค้ดที่เขียนด้วยมือ เพราะนักพัฒนาจะตรวจดูผ่าน "Expand Macro" ## บทสรุป Swift Macros เป็นเครื่องมือที่ทรงพลังในการกำจัด boilerplate โดยไม่สูญเสียความปลอดภัยของชนิดข้อมูลแบบสแตติก เทคโนโลยีนี้ช่วยให้: **ประเด็นสำคัญ:** - ✅ มาโครสองประเภท: freestanding (`#`) และ attached (`@`) - ✅ ห้าบทบาทของ attached: peer, accessor, member, memberAttribute, conformance - ✅ การนำไปใช้ผ่าน swift-syntax และการจัดการ AST - ✅ การทดสอบที่จำเป็นด้วย `SwiftSyntaxMacrosTestSupport` - ✅ ต้องมีแพ็กเกจแยกต่างหากสำหรับการนำไปใช้ - ✅ การดีบักผ่าน "Expand Macro" ใน Xcode - ✅ ข้อความผิดพลาดที่ชัดเจนเป็นปัจจัยสำคัญสำหรับประสบการณ์นักพัฒนา Swift Macros มีประโยชน์เป็นพิเศษในการสร้าง conformance (Equatable, Codable), property wrapper ขั้นสูง และการปรับ API ที่อิง callback ให้รองรับ async/await --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/ios/swift-macros-practical-metaprogramming-examples