μλ νμΈμ! μ€λμ Swift 5.9μμ λμ λ 'λ§€ν¬λ‘(Macros)'μ λν΄ μμλ³΄κ² μ΅λλ€. λ§€ν¬λ‘λ₯Ό μ¬μ©νλ©΄ λ°λ³΅μ μΈ μ½λ μμ±μ μ€μ΄κ³ , μ»΄νμΌ μμ μ μ½λλ₯Ό μλ μμ±ν μ μμ΄ κ°λ° ν¨μ¨μ±κ³Ό μ½λ νμ§μ ν¬κ² ν₯μμν¬ μ μμ΅λλ€.
π λ§€ν¬λ‘λ?
λ§€ν¬λ‘λ μ»΄νμΌ μκ°μ μ½λλ₯Ό μμ±νλ λꡬλ‘, κ°λ°μκ° μ§μ μμ±ν΄μΌ ν λ°λ³΅μ μΈ μ½λλ₯Ό μλμΌλ‘ λ§λ€μ΄μ€λλ€. Swift μ»΄νμΌλ¬λ μ½λλ₯Ό λΉλνκΈ° μ μ λͺ¨λ λ§€ν¬λ‘λ₯Ό νμ₯ν©λλ€.
λ§€ν¬λ‘μ μ£Όμ νΉμ§:
- νμ μ½λλ₯Ό μΆκ°λ§ νκ³ , κΈ°μ‘΄ μ½λλ₯Ό μμ νκ±°λ μμ νμ§ μμ
- λ¬Έλ²μ μΌλ‘ μ ν¨ν Swift μ½λλ₯Ό μμ±
- νμ μμ μ± λ³΄μ₯
- λ§€ν¬λ‘ νμ₯ μ€ μ€λ₯ λ°μ μ μ»΄νμΌ μ€λ₯λ‘ μ²λ¦¬
π λ§€ν¬λ‘μ μ’ λ₯
Swiftλ λ κ°μ§ μ νμ λ§€ν¬λ‘λ₯Ό μ 곡ν©λλ€:
1. λ 립 λ§€ν¬λ‘ (Freestanding Macros)
λ 립 λ§€ν¬λ‘λ μ΄λ¦ μμ ν΄μ κΈ°νΈ(#)λ₯Ό λΆμ¬ νΈμΆνλ©°, λ€λ₯Έ μ μΈμ 첨λΆλμ§ μκ³ λ 립μ μΌλ‘ μ¬μ©λ©λλ€.
func myFunction() {
print("νμ¬ μ€ν μ€μΈ ν¨μ: \(#function)")
#warning("λ¬Έμ κ° μμ΅λλ€")
}
μ΄ μμ μμ:
- #functionμ νμ¬ ν¨μμ μ΄λ¦μΌλ‘ λ체λ©λλ€.
- #warningμ μ¬μ©μ μ μ μ»΄νμΌ κ²½κ³ λ₯Ό μμ±ν©λλ€.
2. μ²¨λΆ λ§€ν¬λ‘ (Attached Macros)
μ²¨λΆ λ§€ν¬λ‘λ μ΄λ¦ μμ μ³ κΈ°νΈ(@)λ₯Ό λΆμ¬ νΈμΆνλ©°, νΉμ μ μΈμ 첨λΆλμ΄ ν΄λΉ μ μΈμ μμ ν©λλ€.
// λ§€ν¬λ‘ μμ΄ μμ±ν μ½λ
struct SundaeToppings: OptionSet {
let rawValue: Int
static let nuts = SundaeToppings(rawValue: 1 << 0)
static let cherry = SundaeToppings(rawValue: 1 << 1)
static let fudge = SundaeToppings(rawValue: 1 << 2)
}
// λ§€ν¬λ‘λ₯Ό μ¬μ©ν κ°κ²°ν μ½λ
@OptionSet<Int>
struct SundaeToppings {
private enum Options: Int {
case nuts
case cherry
case fudge
}
}
@OptionSet λ§€ν¬λ‘λ private μ΄κ±°νμ μΌμ΄μ€λ₯Ό μ½κ³ , κ° μ΅μ μ λν μμλ₯Ό μμ±νλ©°, OptionSet νλ‘ν μ½ μ€μμ±μ μλμΌλ‘ μΆκ°ν©λλ€.
π λ§€ν¬λ‘ νμ₯μ μλ λ°©μ
Swiftκ° λ§€ν¬λ‘λ₯Ό νμ₯νλ κ³Όμ μ λ€μκ³Ό κ°μ΅λλ€:
- μ½λ νμ±: μ»΄νμΌλ¬κ° μ½λλ₯Ό μ½κ³ μΆμ ꡬ문 νΈλ¦¬(AST)λΌλ λ©λͺ¨λ¦¬ ννμ μμ±ν©λλ€.
- λ§€ν¬λ‘ μ€ν: μ»΄νμΌλ¬κ° ASTμ μΌλΆλ₯Ό λ§€ν¬λ‘ ꡬνμ μ μ‘νμ¬ νμ₯μ μμ²ν©λλ€.
- μ½λ λ체: νμ₯λ μ½λλ‘ λ§€ν¬λ‘ νΈμΆμ λ체ν©λλ€.
- μ»΄νμΌ μ§ν: νμ₯λ μμ€ μ½λλ‘ μ»΄νμΌμ κ³μν©λλ€.
μλ₯Ό λ€μ΄, #fourCharacterCode("ABCD") λ§€ν¬λ‘λ 4κΈμ λ¬Έμμ΄μ ν΄λΉνλ λΆνΈ μλ 32λΉνΈ μ μλ‘ λ³νν©λλ€:
let magicNumber = #fourCharacterCode("ABCD")
// νμ₯ ν: let magicNumber = 1145258561 as UInt32
λ§€ν¬λ‘ νμ₯ μ μ€μν 보μ μ‘°μΉ:
- λ§€ν¬λ‘ ꡬνμ λ§€ν¬λ‘ νΈμΆ λΆλΆμ ASTλ§ μ κ·Ό κ°λ₯
- νμΌ μμ€ν μ΄λ λ€νΈμν¬μ μ κ·Όν μ μλ μλλ°μ€ νκ²½μμ μ€ν
- λ§€ν¬λ‘ νμ₯μ μ λ ₯ μΈμ μΈλΆ μμ(μ: νμ¬ μκ°)μ μμ‘΄νμ§ μμμΌ ν¨
π λ§€ν¬λ‘ μ μΈνκΈ°
λ§€ν¬λ‘λ macro ν€μλλ₯Ό μ¬μ©νμ¬ μ μΈν©λλ€:
@attached(member)
@attached(extension, conformances: OptionSet)
public macro OptionSet<RawType>() =
#externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
λ§€ν¬λ‘ μ μΈμ μ£Όμ μμ:
- λ§€ν¬λ‘ μν (Role): @attached λλ @freestanding μμ±μΌλ‘ μ§μ
- μ΄λ¦κ³Ό μ λ€λ¦ λ§€κ°λ³μ: μ μμμμλ OptionSet<RawType>
- ꡬν μμΉ: #externalMacro λ§€ν¬λ‘λ‘ κ΅¬ν λͺ¨λκ³Ό νμ μ μ§μ
λ§€ν¬λ‘ μν μ§μ
μ²¨λΆ λ§€ν¬λ‘λ @attached μμ±μΌλ‘ μν μ μ§μ ν©λλ€:
@attached(member, names: named(RawValue), named(rawValue),
named(`init`), arbitrary)
@attached(extension, conformances: OptionSet)
public macro OptionSet<RawType>() =
#externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
λ 립 λ§€ν¬λ‘λ @freestanding μμ±μΌλ‘ μν μ μ§μ ν©λλ€:
@freestanding(expression)
public macro line<T: ExpressibleByIntegerLiteral>() -> T =
/* ... λ§€ν¬λ‘ ꡬν μμΉ... */
π λ§€ν¬λ‘ ꡬννκΈ°
λ§€ν¬λ‘λ₯Ό ꡬννκΈ° μν΄μλ λ κ°μ§ κ΅¬μ± μμκ° νμν©λλ€:
- λ§€ν¬λ‘ νμ₯μ μννλ νμ
- λ§€ν¬λ‘λ₯Ό APIλ‘ λ ΈμΆνλ λΌμ΄λΈλ¬λ¦¬
Swift Package Managerλ‘ λ§€ν¬λ‘ μμ±νκΈ°
μ λ§€ν¬λ‘ ν¨ν€μ§ μμ±:
swift package init --type macro
κΈ°μ‘΄ νλ‘μ νΈμ λ§€ν¬λ‘ μΆκ°:
- Package.swift νμΌμμ Swift λꡬ λ²μ μ 5.9 μ΄μμΌλ‘ μ€μ
- CompilerPluginSupport λͺ¨λ κ°μ Έμ€κΈ°
- μ΅μ λ°°ν¬ λμμ macOS 10.15 ν¬ν¨
- λ§€ν¬λ‘ ꡬν λ° λΌμ΄λΈλ¬λ¦¬ νκ² μΆκ°
// swift-tools-version: 5.9
import PackageDescription
import CompilerPluginSupport
let package = Package(
name: "MyPackage",
platforms: [.iOS(.v17), .macOS(.v13)],
// ...
dependencies: [
.package(url: "https://github.com/swiftlang/swift-syntax", from: "509.0.0")
],
targets: [
// λ§€ν¬λ‘ ꡬν νκ²
.macro(
name: "MyProjectMacros",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),
// λ§€ν¬λ‘λ₯Ό APIλ‘ λ
ΈμΆνλ λΌμ΄λΈλ¬λ¦¬ νκ²
.target(name: "MyProject", dependencies: ["MyProjectMacros"]),
]
)
λ§€ν¬λ‘ ꡬν μμ
#fourCharacterCode λ§€ν¬λ‘ ꡬν:
import SwiftSyntax
import SwiftSyntaxMacros
public struct FourCharacterCode: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
guard let argument = node.argumentList.first?.expression,
let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
segments.count == 1,
case .stringSegment(let literalSegment)? = segments.first
else {
throw CustomError.message("μ μ λ¬Έμμ΄μ΄ νμν©λλ€")
}
let string = literalSegment.content.text
guard let result = fourCharacterCode(for: string) else {
throw CustomError.message("μ ν¨νμ§ μμ 4κΈμ μ½λμ
λλ€")
}
return "\(raw: result) as UInt32"
}
}
private func fourCharacterCode(for characters: String) -> UInt32? {
guard characters.count == 4 else { return nil }
var result: UInt32 = 0
for character in characters {
result = result << 8
guard let asciiValue = character.asciiValue else { return nil }
result += UInt32(asciiValue)
}
return result
}
enum CustomError: Error { case message(String) }
λ§€ν¬λ‘ νλ¬κ·ΈμΈ μ§μ μ :
import SwiftCompilerPlugin
@main
struct MyProjectMacros: CompilerPlugin {
var providingMacros: [Macro.Type] = [FourCharacterCode.self]
}
π λ§€ν¬λ‘ κ°λ°κ³Ό λλ²κΉ
λ§€ν¬λ‘λ ν μ€νΈ κΈ°λ° κ°λ°μ μ ν©ν©λλ€:
- μΈλΆ μνμ μμ‘΄νμ§ μμ
- λ¬Έμμ΄μμ μ½κ² ꡬ문 λ Έλ μμ± κ°λ₯
- ASTμ description μμ±μΌλ‘ κ²°κ³Ό νμΈ κ°λ₯
ν μ€νΈ μμ :
let source: SourceFileSyntax =
"""
let abcd = #fourCharacterCode("ABCD")
"""
let file = BasicMacroExpansionContext.KnownSourceFile(
moduleName: "MyModule",
fullFilePath: "test.swift"
)
let context = BasicMacroExpansionContext(sourceFiles: [source: file])
let transformedSF = source.expand(
macros:["fourCharacterCode": FourCharacterCode.self],
in: context
)
let expectedDescription =
"""
let abcd = 1145258561 as UInt32
"""
precondition(transformedSF.description == expectedDescription)
π μ€μ©μ μΈ λ§€ν¬λ‘ νμ© μ¬λ‘
λ§€ν¬λ‘λ λ€μκ³Ό κ°μ μμ μ νΉν μ μ©ν©λλ€:
- μμ©κ΅¬ μ½λ μμ±: JSON μ§λ ¬ν, μ½λ© ν€ μμ± λ±
- λμμΈ ν¨ν΄ ꡬν: μ±κΈν€, ν©ν 리, λΉλ ν¨ν΄ λ±
- νλ‘ν μ½ μ€μ μλν: Equatable, Hashable, Codable λ±
- λ‘κΉ λ° λλ²κΉ μ§μ: ν¨μ μΆμ , μ±λ₯ μΈ‘μ λ±
- DSL(Domain-Specific Language) λ§λ€κΈ°: SwiftUI μ€νμΌμ μ μΈμ ꡬ문 λ±
π λ§€ν¬λ‘ μ¬μ© μ μ£Όμμ¬ν
- λ§€ν¬λ‘λ₯Ό κ³Όλνκ² μ¬μ©νμ§ λ§μΈμ: νμν κ²½μ°μλ§ μ¬μ©
- λ§€ν¬λ‘ νμ₯ μ½λλ₯Ό μ΄ν΄νμΈμ: μμ±λ μ½λκ° λ¬΄μμΈμ§ μμμΌ ν¨
- λ§€ν¬λ‘ λ¬Έμνλ₯Ό μ² μ ν νμΈμ: λμ λ°©μκ³Ό μμ±νλ μ½λλ₯Ό λͺ νν μ€λͺ
- ν μ€νΈλ₯Ό μμ±νμΈμ: λ€μν μ λ ₯μ λν λ§€ν¬λ‘ λμμ ν μ€νΈ
- λ§€ν¬λ‘ ꡬνμμ μΈλΆ μνμ μμ‘΄νμ§ λ§μΈμ: κ²°μ μ μΈ κ²°κ³Ό μμ±
π μ 리
Swift λ§€ν¬λ‘λ μ»΄νμΌ μκ°μ μ½λλ₯Ό μμ±νμ¬ λ°λ³΅μ μΈ μμ μ μλννκ³ μ½λ νμ§μ ν₯μμν€λ κ°λ ₯ν λꡬμ λλ€. λ 립 λ§€ν¬λ‘μ μ²¨λΆ λ§€ν¬λ‘λΌλ λ κ°μ§ μ νμ ν΅ν΄ λ€μν μ¬μ© μ¬λ‘λ₯Ό μ§μνλ©°, μ격ν μμ μ± λ³΄μ₯μ μ 곡ν©λλ€.
λ§€ν¬λ‘λ₯Ό μ μ ν νμ©νλ©΄:
- μ½λ μ€λ³΅μ μ€μΌ μ μμ΅λλ€
- μ€μλ₯Ό λ°©μ§ν μ μμ΅λλ€
- μ½λ κ°λ μ±μ ν₯μμν¬ μ μμ΅λλ€
- κ°λ°μ μμ°μ±μ λμΌ μ μμ΅λλ€
Swift λ§€ν¬λ‘λ μμ§ λ°μ μ€μΈ κΈ°λ₯μ΄μ§λ§, μ΄λ―Έ λ§μ μ μ¬λ ₯μ 보μ¬μ£Όκ³ μμ΅λλ€. 볡μ‘ν μμ©κ΅¬ μ½λλ₯Ό κ°κ²°νκ² λ§λ€κ³ λ ννλ ₯ μλ APIλ₯Ό μ€κ³νλ λ° λ§€ν¬λ‘λ₯Ό νμ©ν΄ 보μΈμ!
'π₯ Bread Basics > Swift' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
Swift 곡μ λ¬Έμ μ 리 - νμ₯ (Extensions) (0) | 2025.04.13 |
---|---|
Swift 곡μ λ¬Έμ μ 리 - μ€μ²©λ νμ (Nested Types) (1) | 2025.04.12 |
Swift 곡μ λ¬Έμ μ 리 - νμ μΊμ€ν (Type Casting) (0) | 2025.04.12 |
Obj-C 곡μ λ¬Έμ μ 리 - κ°μ²΄, ν΄λμ€, λ©μμ§ (0) | 2025.04.11 |
Swift 곡μ λ¬Έμ μ 리 - λμμ± (Concurrency) (0) | 2025.04.11 |
Swift 곡μ λ¬Έμ μ 리 - μλ¬ μ²λ¦¬ (Error Handling) (0) | 2025.04.11 |
Swift 곡μ λ¬Έμ μ 리 - μ΅μ λ 체μ΄λ (Optional Chaining) (0) | 2025.04.11 |
Swift 곡μ λ¬Έμ μ 리 - μ΄κΈ°ν ν΄μ (Deinitialization) (1) | 2025.04.11 |